Introduction

Philly is sooooo… hosuing prediction….

This code is built upon the classwork discussed here.

R Setup and Installing Packages

This code chunk handles the essential tasks of loading necessary packages, configuring the Census API key, defining class functions, specifying a color palette, and managing global environment settings.

# Loading libraries

library(tidyverse)
library(tidycensus)
library(sf)
library(kableExtra)
library(tidyr)
library(ggplot2)
library(viridis)
library(stringr)
library(tigris)
library(ggcorrplot)
library(stargazer)
library(dplyr)
library(caTools)
library(spdep)
library(caret)


# Setting parameters for scientific notation

options(scipen=999)
options(tigris_class = "sf")

# Functions and data directory

source("https://raw.githubusercontent.com/urbanSpatial/Public-Policy-Analytics-Landing/master/functions.r")

# Invoking color palettes to be used

palettee <- c('#d7191c','#fdae61','#ffffbf','#abd9e9','#2c7bb6')

# Registering API Key to be used

census_api_key('bf2d507651b5a621dbadd44533fb4f3deaab26bf', overwrite = TRUE)

Loading Data

Data sources used include census and opendata philly

Data provided was cleaned and new variable were created

# Reading Data

data <- 
  st_read("./data/studentData.geojson") %>%
  st_transform('ESRI:102286')

# Dropping columns with no values and filtering values within Philadelphia

data <-  data %>% 
  select(-cross_reference, -date_exterior_condition, -mailing_address_2, -mailing_care_of, -unfinished, -utility, -category_code, -category_code_description, -exempt_land, -separate_utilities, -sewer, - site_type, -house_extension, -street_direction, -suffix, -garage_type, -general_construction )%>% 
  filter(mailing_city_state == "PHILADELPHIA PA" )
## Filtering out 9 rows where year built is not given

data <-  data %>% 
  filter(year_built > 0 )

## Categorizing if a Basement is present

data <- data %>%
  mutate(BasementPresent = case_when(basements == 'A' |
  basements == 'B' |
  basements == 'C' |
  basements == 'D' |
  basements == 'E' |
  basements == 'F' |
  basements == 'G' |
  basements == 'H' |
  basements == 'I' |
  basements == 'J' ~ 1,
  basements == '0' ~ 0))

# Assigning value of 0 to 'NA' rows based on description in metadata

data$BasementPresent[is.na(data$BasementPresent)] <- 0

## Categorizing Basement Type

library(dplyr)

data <- data %>%
  mutate(BasementType = case_when(basements == 'A' ~ 'Full size finished',
  basements == 'B' ~ 'Full size semi-finished',
  basements == 'C' ~ 'Full size unfinished',
  basements == 'D' ~ 'Full size unknown finish',
  basements == 'E' ~ 'Partial size finished',
  basements == 'F' ~ 'Partial size semi-finished',
  basements == 'G' ~ 'Partial size unfinished',
  basements == 'H' ~ 'Partial size unknown finish',
  basements == 'I' ~ 'Unknown size finished',
  basements == 'J' ~ 'Unknown size unfinished',
  basements == '0' ~ 'No basement'))

# Assigning value of 0 to 'NA' rows based on description in metadata

data$BasementType[is.na(data$BasementType)] <- "No basement"
data$basements[is.na(data$basements)] <- 0

## Categorizing based on Central Air

data$central_air <- ifelse(data$central_air == 'Y', 1, 0)

## Categorizing based on Exterior Condition

data <- data %>%
  mutate(ExteriorConditionType = case_when(exterior_condition == '1' |
  exterior_condition == '2' |
  exterior_condition == '3' ~ 'Good Condition',
  exterior_condition == '4' |
  exterior_condition == '5' ~ 'Average Condition',
  exterior_condition == '6' ~ 'Below Average Condition',
  exterior_condition == '7' ~ 'Vacant and Sealed'))

# Assigning value to single 'NA' row based on 'interior_construction' rating for same row

data$ExteriorConditionType[is.na(data$ExteriorConditionType)] <- "Good Condition"
data$exterior_condition[is.na(data$exterior_condition)] <- 3

## Categorizing based on Fireplaces

# Assigning value of 0 to 'NA' rows based on description in metadata

data$fireplaces[is.na(data$fireplaces)] <- 0

## Categorizing based on Fuel Sources

data <- data %>%
  mutate(FuelType = case_when(fuel == 'A' ~ 'Natural Gas Powered',
  fuel == 'B' ~ 'Oil Powered',
  fuel == 'C' ~ 'Electric Powered',
  fuel == 'E' ~ 'Solar Powered',
  fuel == 'G' ~ 'Fuel Source Unknown'))

# Assigning value of Unknown/G to 'NA' rows based on description in metadata

data$FuelType[is.na(data$FuelType)] <- "Fuel Source Unknown"
data$fuel[is.na(data$fuel)] <- "G"

## Categorizing based on Garage Presence

data <- data %>%
  mutate(GaragePresent = case_when(garage_spaces == '0' ~ 0,
  garage_spaces == '1' |
  garage_spaces == '2' |
  garage_spaces == '3' ~ 1))

# Assigning value of 0 to 'NA' rows based on description in metadata

data$GaragePresent[is.na(data$GaragePresent)] <- 0
data$garage_spaces[is.na(data$garage_spaces)] <- 0

## Categorizing based on Garage Types

data <- data %>%
  mutate(GarageType = case_when(garage_spaces == '0' ~ 'No Garage',
  garage_spaces == '1' ~ 'Single Garage',
  garage_spaces == '2' |
  garage_spaces == '3' ~ 'Multiple Garages'))

# Assigning value of 0 to 'NA' rows based on description in metadata

data$GarageType[is.na(data$GarageType)] <- "No Garage"
data$garage_spaces[is.na(data$garage_spaces)] <- 0

## Categorizing based on Exterior Condition

data <- data %>%
  mutate(InteriorConditionType = case_when( interior_condition == '0' |
  interior_condition == '1' |
  interior_condition == '2' |
  interior_condition == '3' ~ 'Good Condition',
  interior_condition == '4' ~ 'Average Condition',
  interior_condition == '5' ~ 'Below Average Condition',
  interior_condition == '6' |
  interior_condition == '7' ~ 'Vacant and/or Sealed'))

# Assigning values to 0 or 'NA' rows based on exterior condition since interior and exterior conditions are equal in almost all cases

data$InteriorConditionType[is.na(data$InteriorConditionType)] <- c(data$ExteriorConditionType)

## Categorizing based on View Type

data <- data %>%
  mutate(ViewType = case_when(view_type == '0' ~ 'Nature of View Unknown',
  view_type == 'I' ~ 'Typical View',
  view_type == 'A' ~ 'Skyline View',
  view_type == 'B' ~ 'River View',
  view_type == 'C' ~ 'Park View',
  view_type == 'D' ~ 'Commercial Area View',
  view_type == 'E' ~ 'Industrial Area View',
  view_type == 'H' ~ 'View of Landmark'))

# Assigning value to NA rows to nature of view unknown

data$ViewType[is.na(data$ViewType)] <- "Nature of View Unknown"
data$view_type[is.na(data$view_type)] <- 0

## Categorizing based on Topography Type

data <- data %>%
  mutate(TopographyType = case_when(topography == 'A' ~ 'Above Street Level Topography',
  topography == 'B' ~ 'Below Street Level Topography',
  topography == 'C' ~ 'Flood Plain Topography',
  topography == 'D' ~ 'Rocky Topography',
  topography == 'E' ~ 'Other Topography',
  topography == 'F' ~ 'Level Topography'))

# Assigning value to NA rows to nature of view unknown

data$TopographyType[is.na(data$TopographyType)] <- "Topography Unknown"
data$topography[is.na(data$topography)] <- 0

## Categorizing based on Parcel Type

data <- data %>%
  mutate(ParcelType = case_when(parcel_shape == 'A' ~ 'Irregular Parcel',
  parcel_shape == 'B' ~ 'Grossly Irregular Parcel',
  parcel_shape == 'C' ~ 'Triangular Parcel',
  parcel_shape == 'D' ~ 'Long Narrow Parcel',
  parcel_shape == 'E' ~ 'Rectangular Parcel'))

# Assigning value to NA rows to nature of view unknown

data$ParcelType[is.na(data$ParcelType)] <- "Parcel Type Unknown"
data$parcel_shape[is.na(data$parcel_shape)] <- 0
data <-  data %>% 
  filter(sale_price > 0)
## Imputing values for missing values of number of bedrooms based on total livable area

# Dropping 32 values of total livable area which are 0 for better prediction

data <-  data %>% 
  filter(total_livable_area > 0 )

# Step 1 - Creating an index of 0 and 1 values for rows that have values for number of bathrooms and rows that do not

data <- data %>%
  mutate(BedroomIndex = case_when(number_of_bedrooms >= 1 ~ 1,
                               number_of_bedrooms < 1 ~ 0))
                               
data$BedroomIndex[is.na(data$BedroomIndex)] <- 0

# Step 2 - Creating a linear regression model relating number of bedrooms and total liveable area

lm(number_of_bedrooms ~ total_livable_area, data=data)

# Step 3 - Imputing new values for missing values of number of bedrooms using regression results

for(i in 1:nrow(data))
{
  if (data$BedroomIndex[i] == 0)
  {
    data$number_of_bedrooms[i] = 2.1605650 + 0.0003057*data$total_livable_area[i]
  }
  }

data$number_of_bedrooms <- round(data$number_of_bedrooms, digits=0)
## Imputing values for missing values of number of rooms based on total livable area

# Step 1 - Creating an index of 0 and 1 values for rows that have values for number of rooms and rows that do not

data <- data %>%
  mutate(RoomIndex = case_when(number_of_rooms >= 1 ~ 1,
                               number_of_rooms < 1 ~ 0))
                               
data$RoomIndex[is.na(data$RoomIndex)] <- 0

# Step 2 - Creating a linear regression model relating number of rooms and total livable area

lm(number_of_rooms ~ total_livable_area, data=data)

# Step 3 - Imputing new values for missing values of number of rooms using regression results

for(i in 1:nrow(data))
{
  if (data$RoomIndex[i] == 0)
  {
    data$number_of_rooms[i] = 4.316503 + 0.001319*data$total_livable_area[i]
  }
  }

data$number_of_rooms <- round(data$number_of_rooms, digits=0)
## Imputing values for missing values of number of bathrooms based on total livable area

# Step 1 - Creating an index of 0 and 1 values for rows that have values for number of bathrooms and rows that do not

data <- data %>%
  mutate(BathroomIndex = case_when(number_of_bathrooms >= 1 ~ 1,
                               number_of_bathrooms < 1 ~ 0))

data$BathroomIndex[is.na(data$BathroomIndex)] <- 0

# Step 2 - Creating a linear regression model relating number of bathrooms and  total livable area

lm(number_of_bathrooms ~ total_livable_area, data=data)

# Step 3 - Imputing new values for missing values of number of bathrooms using regression results

for(i in 1:nrow(data))
{
  if (data$BathroomIndex[i] == 0)
  {
    data$number_of_bathrooms[i] = 0.6426310 + 0.0003283*data$total_livable_area[i]
  }
  }

data$number_of_bathrooms <- round(data$number_of_bathrooms, digits=0)
data <- 
  data %>%
  mutate(PricePerSqft = (sale_price/total_livable_area))
data_og <- data

data <- data %>% 
  select(objectid, assessment_date, year_built, building_code, building_code_description, pin, building_code_new, building_code_description_new,  census_tract, geographic_ward, zoning, location, street_name, street_code, street_designation, zip_code, house_number, depth, frontage, central_air, fireplaces, fuel, FuelType, basements, BasementPresent, BasementType, garage_spaces, GaragePresent, GarageType, exterior_condition, ExteriorConditionType, interior_condition, InteriorConditionType, number_of_bathrooms, number_of_bedrooms, number_of_rooms, number_stories, total_livable_area, view_type, ViewType, topography, TopographyType, parcel_shape, ParcelType, sale_date, sale_year, sale_price, PricePerSqft, musaID, toPredict, geometry )
data <-  data %>% 
  filter(number_of_bedrooms <10, number_of_rooms<15, ((number_of_bathrooms+number_of_bedrooms) < number_of_rooms), PricePerSqft<1700, total_livable_area<10000)

Census Data

The years chosen for analysis are 2021 because covid recovery most recent to mkae more acccurate predicitons

The variables chosen for this analysis include:

  1. income because -

acs_variable_list.2021 <- load_variables(2021, #year
                                         "acs5", #five year ACS estimates
                                         cache = TRUE)
# 2021, A

# Retrieve ACS data for Philadelphia tracts in 2020
tracts21 <- get_acs(
  geography = "tract",
  variables = c(
    "B01003_001",   # Total Population
    "B19013_001",   # Median Household Income
    "B25058_001",   # Median Rent
    "B25008_002",   # Owner-Occupied Units
    "B25008_003",   # Renter-Occupied Units
    "B07001_032",   # Same House 75 Years Ago
    "B07001_017",   # Same House 1 Year Ago
    "B25088_003",   # Median Selected Monthly Owner Costs (homes without a mortgage)
    "B25088_002",   # Median Selected Monthly Owner Costs (homes with a mortgage)
    "B25064_001",   # Median Gross Rent (rent and utilities)
    "B25117_001",   # Percentage of Housing Units with heat
    "B15003_022",   # Educational Attainment: Bachelor's Degree
    "B17001_002",   # Percentage of Population Below the Poverty Level
    "B28002_004",   # Percentage of Housing Units with High-Speed Internet
    "B25044_003",   # Percentage of Housing Units with No Vehicle Available
    "B02001_002",   # Race and Ethnicity: White Alone
    "B02001_003",   # Race and Ethnicity: Black or African American Alone
    "B03001_003"    # Hispanic or Latino Origin of Population
  ),
  year = 2021,
  state = "PA",
  county = "Philadelphia",
  geometry = TRUE,
  output = "wide"
)%>%
  select(-NAME, -ends_with("M")) %>%
  rename(totalpop = B01003_001E,                           # Total Population
         med_income = B19013_001E,                         # Median Household Income
         med_rent = B25058_001E,                           # Median Rent
         owner_units = B25008_002E,                        # Owner-Occupied Units
         renter_units = B25008_003E,                       # Renter-Occupied Units
         same_house_75 = B07001_032E,                      # Same House 75 Years Ago
         same_house_1 = B07001_017E,                       # Same House 1 Year Ago
         monthly_costs_no_mortgage = B25088_003E,          # Median Selected Monthly Owner Costs (homes without a mortgage)
         monthly_costs_with_mortgage = B25088_002E,        # Median Selected Monthly Owner Costs (homes with a mortgage)
         med_gross_rent = B25064_001E,                     # Median Gross Rent (rent and utilities)
         housing_units_with_heat = B25117_001E,            # Percentage of Housing Units with heat
         edu_bachelors = B15003_022E,                      # Educational Attainment: Bachelor's Degree
         pop_below_poverty = B17001_002E,                  # Percentage of Population Below the Poverty Level
         housing_units_high_speed_internet = B28002_004E,  # Percentage of Housing Units with High-Speed Internet
         housing_units_no_vehicle = B25044_003E,           # Percentage of Housing Units with No Vehicle Available
         race_white = B02001_002E,                         # Race and Ethnicity: White Alone
         race_black = B02001_003E,                         # Race and Ethnicity: Black or African American Alone
         hispanic_latino = B03001_003E                     # Race and Ethnicity: Hispanic or Latino
         )

# Transform the data to ESRI:102728 projection
tracts21 <- tracts21 %>% st_transform(st_crs(data))

Open Data philly

private schools proximity, parks and landmarks, floodplains, daily arrests, litter score, heat index

philly rising boundaries?

# Adding data on Philadelphia Schools

PhillySchools <-
   st_read("./data/Schools.geojson") %>%
  filter(TYPE_SPECIFIC == "PRIVATE") %>%
  st_transform(st_crs(tracts21))

# Mapping nearest schools 

nearest_fts <- sf::st_nearest_feature(data, PhillySchools)

# Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(data)
y <- rsgeo::as_rsgeo(PhillySchools)

# Calculating distance

data$dist_to_pvt_schools <- rsgeo::distance_euclidean_pairwise(x, y[nearest_fts])

# Adding data on Philadelphia landmarks 

PhillyLandmarks <-
 st_read("https://services.arcgis.com/fLeGjb7u4uXqeF9q/arcgis/rest/services/Landmark_Points/FeatureServer/0/query?outFields=*&where=1%3D1&f=geojson")%>%
  st_transform(st_crs(tracts21))

# Mapping nearest landmarks 

nearest_fts <- sf::st_nearest_feature(data, PhillyLandmarks)

# Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(data)
y <- rsgeo::as_rsgeo(PhillyLandmarks)

# Calculating distance

data$dist_to_landmarks <- rsgeo::distance_euclidean_pairwise(x, y[nearest_fts])

# Adding data on Philadelphia Commercial Corridors 

PhillyComCorr <-
  st_read("./data/Commercial_Corridors.geojson") %>%
  st_transform(st_crs(tracts21))

# Is it within the commercial corridor?

data$within_com_corr <- ifelse(st_within(data, PhillyComCorr), 1, 0)

data <- data %>%
  mutate(within_com_corr = ifelse(is.na(within_com_corr), 0, 1))

# Mapping nearest landmarks 

nearest_fts <- sf::st_nearest_feature(data, PhillyComCorr)

# Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(data)
y <- rsgeo::as_rsgeo(PhillyComCorr)

# Calculating distance

data$dist_to_comm_corr <- rsgeo::distance_euclidean_pairwise(x, y[nearest_fts])

# Adding data on Philadelphia's Litter Index

PhillyLitter <-
  st_read("./data/Litter_Index.geojson") %>%
  st_transform(st_crs(tracts21))

# Joining the litter score

data <- 
 st_join(data,(PhillyLitter %>%
          select(-OBJECTID, -Shape__Area, -Shape__Length )%>%
          rename(litter = SCORE))) 

# Adding data on Philadelphia's Flood Plain

PhillyFlood <- 
  st_read("./data/FEMA_100_flood_Plain.geojson") %>%
  st_transform(st_crs(tracts21))

# Is it within the floodplain?

data$within_flood <- ifelse(st_within(data, PhillyFlood), 1, 0)

data <- data %>%
  mutate(within_flood = ifelse(is.na(within_flood), 0, 1))

# Mapping nearest floodplain 

nearest_fts <- sf::st_nearest_feature(data, PhillyFlood)

# Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(data)
y <- rsgeo::as_rsgeo(PhillyFlood)

# Calculating distance

data$dist_to_floodplain <- rsgeo::distance_euclidean_pairwise(x, y[nearest_fts])

Exploratory Data Analysis

Mapping Internal Variables

##Mapping Internal Variables
# Mapping sale price

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .75) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\n Sale Price", 
                   na.value = NA) +
  labs(title="Properties by Sale Price", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.1.1") +
  mapTheme()

# Mapping Size

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = data, aes(colour = q5(total_livable_area)), 
          show.legend = "point", size = .75) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "total_livable_area"),
                   name="Quintile Breaks:\nArea in Sq Ft", 
                   na.value = NA) +
  labs(title="Properties by Size", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.1.2") +
  mapTheme()

# Mapping Interior Condition

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = data, aes(colour = (InteriorConditionType)), 
          show.legend = "point", size = .5) +
  scale_colour_manual(values = palettee, name="Interior Condition",
                   na.value = NA) +
  labs(title="Properties by Internal Condition", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.1.3") +
  mapTheme()

Mapping External Variables

## Mapping External Variables

# Mapping properties around schools

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .75, alpha=0.3) + 
  geom_sf(data = PhillySchools, colour = "black", size = 1.5, alpha=0.6) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\nSale Prices", 
                   na.value = NA) + 
  labs(title="Properties Around Schools", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.2.1") +
  mapTheme()

# Mapping properties around commercial corridors

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .75, alpha=0.3) + 
  geom_sf(data = PhillyComCorr, colour = "black", fill="black", alpha=0.6) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\nSale Prices", 
                   na.value = NA) + 
  labs(title="Properties Around Commercial Corridors", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.2.2") +
  mapTheme()

# Mapping properties around landmarks

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .75, alpha=0.3) + 
  geom_sf(data = PhillyLandmarks, colour = "black", fill="black", size = .75, alpha=0.6) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\nSale Prices", 
                   na.value = NA) + 
  labs(title="Properties Around Landmarks", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.2.3") +
  mapTheme()

# Mapping properties around floodplains

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .75, alpha=0.3) + 
  geom_sf(data = PhillyFlood, colour = "black", fill="black", size = .75, alpha=0.6) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\nSale Prices", 
                   na.value = NA) + 
  labs(title="Properties Around Flood Plain", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.2.4") +
  mapTheme()

#library(gridExtra)

#grid.arrange(
  #b1,
  #b2,
  #b3,
  #b4,
  #nrow = 2,
  #widths=c(4,4),
  #top = "Title of the page"
  #)

Mapping Demographic Variables

tracts21 <- 
  tracts21 %>%
  mutate(PctWhite = ((race_white/totalpop)*100),
         PctBlack = ((race_black/totalpop)*100),
         PctHispanic = ((hispanic_latino/totalpop)*100),
         PctBachelors = ((edu_bachelors/totalpop)*100),
         PctPoverty = ((pop_below_poverty/totalpop)*100))
## Mapping Demographic Variables

# Mapping median income around sold properties


ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = tracts21, aes(fill = q5(med_income)), color = "transparent", alpha=0.5) +
    scale_fill_brewer(palette = "BuPu",
                      labels = qBr(tracts21, "med_income"),
                      name = "Median Income\nQuintile Breaks") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .2) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\nSale Prices", 
                   na.value = NA) + 
  labs(title="Median Income Around Sold Properties", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.3.1")

# Mapping white population around sold properties

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = tracts21, aes(fill = q5(PctWhite)), color = "transparent", alpha=0.5) +
    scale_fill_brewer(palette = "BuPu",
                      labels = qBr(tracts21, "PctWhite"),
                      name = "% White Population\nQuintile Breaks") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .2) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\nSale Prices", 
                   na.value = NA) + 
  labs(title="% White Population Around Sold Properties", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.3.2")

# Mapping black population around sold properties

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = tracts21, aes(fill = q5(PctBlack)), color = "transparent", alpha=0.5) +
    scale_fill_brewer(palette = "BuPu",
                      labels = qBr(tracts21, "PctBlack"),
                      name = "% Black Population\nQuintile Breaks") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .2) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\nSale Prices", 
                   na.value = NA) + 
  labs(title="% Black Population Around Sold Properties", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.3.3")

# Mapping population with bachelor's degree around sold properties

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = tracts21, aes(fill = q5(PctBachelors)), color = "transparent", alpha=0.5) +
    scale_fill_brewer(palette = "BuPu",
                      labels = qBr(tracts21, "PctBachelors"),
                      name = "% Bachelor's Degree\nQuintile Breaks") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .2) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\nSale Prices", 
                   na.value = NA) + 
  labs(title="% Population with Bachelor's Degree Around Sold Properties", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.3.4")

# Mapping population in poverty around sold properties

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "darkgrey") +
  geom_sf(data = tracts21, aes(fill = q5(PctPoverty)), color = "transparent", alpha=0.5) +
    scale_fill_brewer(palette = "BuPu",
                      labels = qBr(tracts21, "PctPoverty"),
                      name = "% Pop in Poverty\nQuintile Breaks") +
  geom_sf(data = data, aes(colour = q5(sale_price)), 
          show.legend = "point", size = .2) +
  scale_colour_manual(values = palettee,
                   labels=qBr(data, "sale_price"),
                   name="Quintile Breaks:\nSale Prices", 
                   na.value = NA) + 
  labs(title="% Population in Poverty Around Sold Properties", subtitle = "Philadelphia 2022-2023", 
      caption="Figure 3.3.5")+
  mapTheme()

# joining census data

data <- 
  st_join(data, tracts21)
InternalVariables <- data 

InternalVariables <- st_drop_geometry(InternalVariables)

InternalVariables <- InternalVariables %>%
  dplyr::select("sale_price", "PricePerSqft", "total_livable_area", "year_built", "number_of_rooms", "number_of_bathrooms", "number_of_bedrooms") 

stargazer(as.data.frame(InternalVariables), type="text", digits=1, title = "Descriptive Statistics for Philadelphia Homes Internal Variables (Figure 4.1)", out = "Training_PHLInternal.txt")
## 
## Descriptive Statistics for Philadelphia Homes Internal Variables (Figure 4.1)
## ===============================================================
## Statistic             N      Mean    St. Dev.   Min      Max   
## ---------------------------------------------------------------
## sale_price          18,047 283,118.2 229,281.9 11,000 5,900,000
## PricePerSqft        18,047   209.9     121.7    7.2    1,605.1 
## total_livable_area  18,047  1,333.7    516.9    186     7,391  
## year_built          18,047  1,937.8    29.8    1,750    2,024  
## number_of_rooms     18,047    6.1       0.7      3       14    
## number_of_bathrooms 18,047    1.2       0.4      1        5    
## number_of_bedrooms  18,047    2.9       0.6      1        8    
## ---------------------------------------------------------------
DemographicVariables <- data 

DemographicVariables <- st_drop_geometry(DemographicVariables)

DemographicVariables <- DemographicVariables %>%
  dplyr::select("PctWhite", "PctBlack", "PctHispanic", "PctBachelors", "PctPoverty", "med_income") 

stargazer(as.data.frame(DemographicVariables), type="text", digits=1, title = "Descriptive Statistics for Philadelphia Homes Spatial Variables (Figure 4.1)", out = "Training_PHLSpatial.txt")
## 
## Descriptive Statistics for Philadelphia Homes Spatial Variables (Figure 4.1)
## ====================================================
## Statistic      N      Mean   St. Dev.  Min     Max  
## ----------------------------------------------------
## PctWhite     18,046   44.1     30.6    0.0    95.7  
## PctBlack     18,046   35.7     33.2    0.0    98.9  
## PctHispanic  18,046   15.4     18.8    0.0    91.7  
## PctBachelors 18,046   14.1     10.0    0.0    42.3  
## PctPoverty   18,046   20.4     13.1    0.0    65.1  
## med_income   17,889 61,334.6 28,999.7 11,955 210,322
## ----------------------------------------------------

title

#not needed 

#plotting the correlations 

st_drop_geometry(data) %>% 
  dplyr::select(sale_price, med_income, dist_to_pvt_schools) %>%
  gather(Variable, Value, -sale_price) %>% 
   ggplot(aes(Value, sale_price)) +
     geom_point(size = .5) + geom_smooth(method = "lm", se=F, colour = "#FA7800") +
     facet_wrap(~Variable, ncol = 3, scales = "free") +
     labs(title = "Price as a function of continuous variables") +
     plotTheme()

variables <- c(
  "sale_price", "dist_to_pvt_schools", "number_of_bathrooms",
  "number_of_bedrooms", "med_income", "within_flood",
  "within_com_corr", "race_white", "race_black", "total_livable_area",  "PricePerSqft", "year_built", "number_of_rooms", "total_livable_area", "PctBachelors", "PctPoverty"
)

numericVars <- data %>%
  st_drop_geometry(data) %>%
  select(variables)%>%
  na.omit()

ggcorrplot(
  round(cor(numericVars), 1), 
  p.mat = cor_pmat(numericVars),
  colors = c('#d7191c','#ffffbf','#2c7bb6'),
  type="lower",
  insig = "blank") +  
    labs(title = "Correlation across numeric variables") 

#  plotTheme()

Regressions

#not sure if all this is needed

#here we do it with the complete dataset for some reason? 

reg1 <- lm(sale_price ~ ., data = st_drop_geometry(data) %>%  
             #dplyr::filter(toPredict == "MODELLING") %>%
                                 dplyr::select(variables))

summary(reg1)
## 
## Call:
## lm(formula = sale_price ~ ., data = st_drop_geometry(data) %>% 
##     dplyr::select(variables))
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1057270   -17107     4827    18240  3386827 
## 
## Coefficients:
##                          Estimate    Std. Error t value             Pr(>|t|)
## (Intercept)         -491036.01827   45061.06899 -10.897 < 0.0000000000000002
## dist_to_pvt_schools       2.68313       1.27224   2.109             0.034960
## number_of_bathrooms   29907.30316    1761.42288  16.979 < 0.0000000000000002
## number_of_bedrooms     6725.65748    1431.87884   4.697   0.0000026586241083
## med_income                0.23855       0.04208   5.669   0.0000000145428056
## within_flood          15476.38644   10145.58557   1.525             0.127169
## within_com_corr       14061.24579    2593.85211   5.421   0.0000000600380590
## race_white               -2.21149       0.58952  -3.751             0.000176
## race_black               -2.46430       0.56356  -4.373   0.0000123400448557
## total_livable_area      222.62550       2.82175  78.896 < 0.0000000000000002
## PricePerSqft           1325.15636       7.64692 173.293 < 0.0000000000000002
## year_built               21.49225      22.69030   0.947             0.343550
## number_of_rooms       14187.32440    1875.29045   7.565   0.0000000000000405
## PctBachelors           -590.37686     114.36164  -5.162   0.0000002464584394
## PctPoverty              690.12957      77.77504   8.873 < 0.0000000000000002
##                        
## (Intercept)         ***
## dist_to_pvt_schools *  
## number_of_bathrooms ***
## number_of_bedrooms  ***
## med_income          ***
## within_flood           
## within_com_corr     ***
## race_white          ***
## race_black          ***
## total_livable_area  ***
## PricePerSqft        ***
## year_built             
## number_of_rooms     ***
## PctBachelors        ***
## PctPoverty          ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 84710 on 17874 degrees of freedom
##   (158 observations deleted due to missingness)
## Multiple R-squared:  0.864,  Adjusted R-squared:  0.8639 
## F-statistic:  8113 on 14 and 17874 DF,  p-value: < 0.00000000000000022
# pretty regression

print(stargazer(reg1, type="text", digits=1, title="Linear Model of Training Dataset (Figure 5)", out = "Training LM.txt"))
## 
## Linear Model of Training Dataset (Figure 5)
## ===============================================
##                         Dependent variable:    
##                     ---------------------------
##                             sale_price         
## -----------------------------------------------
## dist_to_pvt_schools            2.7**           
##                                (1.3)           
##                                                
## number_of_bathrooms         29,907.3***        
##                              (1,761.4)         
##                                                
## number_of_bedrooms          6,725.7***         
##                              (1,431.9)         
##                                                
## med_income                    0.2***           
##                               (0.04)           
##                                                
## within_flood                 15,476.4          
##                             (10,145.6)         
##                                                
## within_com_corr             14,061.2***        
##                              (2,593.9)         
##                                                
## race_white                    -2.2***          
##                                (0.6)           
##                                                
## race_black                    -2.5***          
##                                (0.6)           
##                                                
## total_livable_area           222.6***          
##                                (2.8)           
##                                                
## PricePerSqft                1,325.2***         
##                                (7.6)           
##                                                
## year_built                     21.5            
##                               (22.7)           
##                                                
## number_of_rooms             14,187.3***        
##                              (1,875.3)         
##                                                
## PctBachelors                 -590.4***         
##                               (114.4)          
##                                                
## PctPoverty                   690.1***          
##                               (77.8)           
##                                                
## Constant                   -491,036.0***       
##                             (45,061.1)         
##                                                
## -----------------------------------------------
## Observations                  17,889           
## R2                              0.9            
## Adjusted R2                     0.9            
## Residual Std. Error    84,705.1 (df = 17874)   
## F Statistic         8,112.5*** (df = 14; 17874)
## ===============================================
## Note:               *p<0.1; **p<0.05; ***p<0.01
##  [1] ""                                               
##  [2] "Linear Model of Training Dataset (Figure 5)"    
##  [3] "==============================================="
##  [4] "                        Dependent variable:    "
##  [5] "                    ---------------------------"
##  [6] "                            sale_price         "
##  [7] "-----------------------------------------------"
##  [8] "dist_to_pvt_schools            2.7**           "
##  [9] "                               (1.3)           "
## [10] "                                               "
## [11] "number_of_bathrooms         29,907.3***        "
## [12] "                             (1,761.4)         "
## [13] "                                               "
## [14] "number_of_bedrooms          6,725.7***         "
## [15] "                             (1,431.9)         "
## [16] "                                               "
## [17] "med_income                    0.2***           "
## [18] "                              (0.04)           "
## [19] "                                               "
## [20] "within_flood                 15,476.4          "
## [21] "                            (10,145.6)         "
## [22] "                                               "
## [23] "within_com_corr             14,061.2***        "
## [24] "                             (2,593.9)         "
## [25] "                                               "
## [26] "race_white                    -2.2***          "
## [27] "                               (0.6)           "
## [28] "                                               "
## [29] "race_black                    -2.5***          "
## [30] "                               (0.6)           "
## [31] "                                               "
## [32] "total_livable_area           222.6***          "
## [33] "                               (2.8)           "
## [34] "                                               "
## [35] "PricePerSqft                1,325.2***         "
## [36] "                               (7.6)           "
## [37] "                                               "
## [38] "year_built                     21.5            "
## [39] "                              (22.7)           "
## [40] "                                               "
## [41] "number_of_rooms             14,187.3***        "
## [42] "                             (1,875.3)         "
## [43] "                                               "
## [44] "PctBachelors                 -590.4***         "
## [45] "                              (114.4)          "
## [46] "                                               "
## [47] "PctPoverty                   690.1***          "
## [48] "                              (77.8)           "
## [49] "                                               "
## [50] "Constant                   -491,036.0***       "
## [51] "                            (45,061.1)         "
## [52] "                                               "
## [53] "-----------------------------------------------"
## [54] "Observations                  17,889           "
## [55] "R2                              0.9            "
## [56] "Adjusted R2                     0.9            "
## [57] "Residual Std. Error    84,705.1 (df = 17874)   "
## [58] "F Statistic         8,112.5*** (df = 14; 17874)"
## [59] "==============================================="
## [60] "Note:               *p<0.1; **p<0.05; ***p<0.01"
## Creating the training and test set
modelling_data <- data %>% filter(toPredict == "MODELLING")

set.seed(999) #makes sure data is split same way every time

## Splitting the data
split <- sample.split(modelling_data$objectid, SplitRatio = 0.75)

## Creating the training and test sets
train_data <- modelling_data[split,]
test_data <- modelling_data[!split,]
#regression on training data

reg2 <- lm(sale_price ~ ., data = st_drop_geometry(train_data) %>%  
                                 dplyr::select(variables))

## Plot regression (didnt work)
# effect_plot(reg2, pred = total_livable_area, interval = TRUE, plot.points = TRUE)


test_data <-
  test_data %>%
  mutate(Price.Predict = predict(reg2, test_data),
         Price.Error = Price.Predict - sale_price,
         Price.AbsError = abs(Price.Predict - sale_price),
         Price.APE = (abs(Price.Predict - sale_price)) / Price.Predict)
# table of MAE and MAPE

## Accuracy - Mean Absolute Error
MAE <- data.frame(mean(test_data$Price.AbsError, na.rm = T))
MAPE <- data.frame(mean(test_data$Price.APE, na.rm = T)) #MAPE

reg.MAE.MAPE <- 
  cbind(MAE, MAPE) %>%
  kable(caption = "Regression MAE & MAPE (Figure 5.1)") %>%
  kable_styling("striped",full_width = F) 

reg.MAE.MAPE
Regression MAE & MAPE (Figure 5.1)
mean.test_data.Price.AbsError..na.rm…T. mean.test_data.Price.APE..na.rm…T.
36016.46 -0.1245474
test_data <-
  test_data %>%
  filter(sale_price < 10000000)

## K-Fold: Generalizability Cross-Validation

fitControl <- trainControl(method = "cv", number = 100)
set.seed(369)

reg.cv <- 
  train(sale_price ~ ., data = st_drop_geometry(test_data) %>% 
                                dplyr::select(variables),
method = "lm", trControl = fitControl, na.action = na.pass)

#This needs to be work-shopped. It stopped working and I don't know why. The code without an object assigned also outputs.

#reg.cv$resample[1:5,]

#mean(reg.cv$resample[,3])

#reg.cv$resample[7,]


stargazer(as.data.frame(reg.cv$resample), type="text", digits=1, title="Cross Validation Results (5.2)", out = "CV.txt") #all cv
## 
## Cross Validation Results (5.2)
## ==================================================
## Statistic  N    Mean   St. Dev.   Min       Max   
## --------------------------------------------------
## RMSE      100 67,150.0 36,690.0 28,938.2 190,261.1
## Rsquared  100   0.9      0.1      0.6       1.0   
## MAE       100 33,472.9 9,587.9  20,412.6 74,159.9 
## --------------------------------------------------
#plotting the cross validation stuff

ggplot(reg.cv$resample, aes(x=MAE)) +
  geom_histogram(fill = "blue") +
  labs(title = "Cross Validation Tests in Mean Average Error", caption="Figure 5.3") +
  plotTheme()

test_data %>%
  dplyr::select(Price.Predict, sale_price) %>%
    ggplot(aes(sale_price, Price.Predict)) +
  geom_point() +
  stat_smooth(aes(sale_price, sale_price), 
             method = "lm", se = FALSE, size = 1, colour="#d7191c") + 
  stat_smooth(aes(Price.Predict, sale_price), 
              method = "lm", se = FALSE, size = 1, colour="#2c7bb6") +
  labs(title="Predicted Sale Price as a Function of Observed Price",
       subtitle="Red line represents a perfect prediction; Blue line represents prediction",
       caption = "Figure 6.2") +
  plotTheme()

#use the same thng for wights and morans i 
#move the filters to this cel too 


# Spatial Lag for price
coords <- st_coordinates(test_data) 
neighborList <- knn2nb(knearneigh(coords, 5))
spatialWeights <- nb2listw(neighborList, style="W", zero.policy = TRUE)
test_data$lagPrice <- lag.listw(spatialWeights, test_data$sale_price)

# Spatial lag for errors
coords.test <-  st_coordinates(test_data) 
neighborList.test <- knn2nb(knearneigh(coords.test, 5))
spatialWeights.test <- nb2listw(neighborList.test, style="W")

test_data <- test_data %>%
  mutate(lagPriceError = lag.listw(spatialWeights.test, Price.Error, NAOK = TRUE))

# Filtering greater than 0 values

test_data_filter <- test_data %>%
  filter(Price.Error > 0, lagPriceError > 0)

# Moran's I

ggplot(data = test_data_filter, aes(lagPriceError, Price.Error)) +
  geom_point(size = .85,colour = "black") + 
  geom_smooth(method = "lm",colour = "red",size = 1.2) +
  labs(title="Price Errors",
       caption = "Figure 6.1") +
  plotTheme()

moranTest <- moran.mc(na.omit(test_data$Price.Error),
                      spatialWeights, nsim = 999)  

ggplot(as.data.frame(moranTest$res[c(1:999)]), aes(moranTest$res[c(1:999)])) +
  geom_histogram(binwidth = 0.01) +
  geom_vline(aes(xintercept = moranTest$statistic), colour = "#d7191c",size=1) +
  scale_x_continuous(limits = c(-1, 1)) +
  labs(title="Observed and permuted Moran's I",
       subtitle= "Observed Moran's I in red",
       caption = "Figure 6.3",
       x="Moran's I",
       y="Count") +
  plotTheme()

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "grey89") +
  geom_sf(data = test_data, aes(colour = q5(Price.AbsError)), 
          show.legend = "point", size = .75) +
  scale_colour_manual(values = palettee,
                   labels=qBr(test_data,"Price.AbsError"),
                   name="Quintile\nBreaks") +
  labs(title="Map of Price Absolute Errors, Boulder CO, 2019-2021", 
      caption="Figure 6.4") +
  mapTheme()

ggplot() +
  geom_sf(data = tracts21, fill = "grey89", color = "grey89") +
  geom_sf(data = test_data, aes(colour = q5(Price.AbsError)), 
          show.legend = "point", size = .75) +
  scale_colour_manual(values = palettee,
                   labels=qBr(test_data,"Price.Predict"),
                   name="Quintile\nBreaks") +
  labs(title="predicted sale price", 
      caption="Figure 6.4") +
  mapTheme()

#this is wrong 

st_drop_geometry(test_data) %>%
  group_by(census_tract) %>%
  summarize(MAPE = mean(Price.APE, na.rm = T)) %>%
  ungroup() %>% 
  cross_join( tracts21)%>%
    st_sf() %>%
    ggplot() + 
   geom_sf(aes(fill = MAPE)) +
      scale_fill_gradient(low = palettee[5], high = palettee[1],
                          name = "MAPE") +
  geom_sf(data = test_data, colour = "black", show.legend = "point", size = .5) +
      labs(title = "Mean test set MAPE by Block Groups",
           caption = "Figure 7.3") +
      mapTheme()

test_data <-
  test_data %>%
  group_by(census_tract) %>%
  mutate(MeanPrice = mean(sale_price))

test_data <-
  test_data %>%
  group_by(census_tract) %>%
  mutate(MAPE = mean(Price.APE)) 

#This now seems correct versus the old version - woo! 

ggplot(test_data) +
  geom_point(aes(MeanPrice, MAPE)) +
  geom_smooth(method = "lm", aes(MeanPrice, MAPE), se = FALSE, colour = "green") +
  labs(title = "MAPE by Block Group as a function of mean price by Block Group",
       x = "Mean Home Price", y = "MAPE",
       caption = "Figure 7.5") +
  plotTheme()

LS0tDQp0aXRsZTogIlByZWRpY3RpbmcgSG91c2luZyBQcmljZXMgaW4gUGhpbGFkZWxwaGlhLCBQQSINCmF1dGhvcjogIlNhbXJpZGRoaSBLaGFyZSwgUm9zaGluaSBHYW5lc2giDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiBqb3VybmFsICANCi0tLQ0KDQojIEludHJvZHVjdGlvbg0KDQpQaGlsbHkgaXMgc29vb29vLi4uIGhvc3VpbmcgcHJlZGljdGlvbi4uLi4gIA0KDQo8aW1nIHNyYz0iLi9pbWFnZXMvMzNyZC1TdHJlZXQtQ29ybmVyLW9mLVdlc3QtTGVoaWdoLUF2ZS5qcGciIHdpZHRoPSIzMDAiIGFsaWduPSJyaWdodCIgc3R5bGU9ImRpc3BsYXk6IGlubGluZTsgbWFyZ2luOiAwIDEwcHg7Ii8+DQoNClRoaXMgY29kZSBpcyBidWlsdCB1cG9uIHRoZSBjbGFzc3dvcmsgZGlzY3Vzc2VkIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vbWFmaWNobWFuL211c2FfNTA4MF8yMDIzL3RyZWUvbWFpbikuDQoNCmBgYHtyIFNldHVwIEtuaXR0aW5nIFBhcmFtZXRlcnMsIGluY2x1ZGU9RkFMU0V9DQogIGtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgICBlY2hvID0gVFJVRSwNCiAgICB3YXJuaW5nID0gRkFMU0UsDQogICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgIyBvdXQud2lkdGggPSAnMTAwJScsDQogICAgZmlnLndpZHRoPTE0LA0KICAgIGZpZy5yZXRpbmEgPTMNCiAgKQ0KYGBgDQoNCiMjIFIgU2V0dXAgYW5kIEluc3RhbGxpbmcgUGFja2FnZXMNCg0KVGhpcyBjb2RlIGNodW5rIGhhbmRsZXMgdGhlIGVzc2VudGlhbCB0YXNrcyBvZiBsb2FkaW5nIG5lY2Vzc2FyeSBwYWNrYWdlcywgY29uZmlndXJpbmcgdGhlIENlbnN1cyBBUEkga2V5LCBkZWZpbmluZyBjbGFzcyBmdW5jdGlvbnMsIHNwZWNpZnlpbmcgYSBjb2xvciBwYWxldHRlLCBhbmQgbWFuYWdpbmcgZ2xvYmFsIGVudmlyb25tZW50IHNldHRpbmdzLg0KDQpgYGB7ciBTZXQgdXAgUGFja2FnZXMsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KDQojIExvYWRpbmcgbGlicmFyaWVzDQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeSh0aWR5Y2Vuc3VzKQ0KbGlicmFyeShzZikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHZpcmlkaXMpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KHRpZ3JpcykNCmxpYnJhcnkoZ2djb3JycGxvdCkNCmxpYnJhcnkoc3RhcmdhemVyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoY2FUb29scykNCmxpYnJhcnkoc3BkZXApDQpsaWJyYXJ5KGNhcmV0KQ0KDQoNCiMgU2V0dGluZyBwYXJhbWV0ZXJzIGZvciBzY2llbnRpZmljIG5vdGF0aW9uDQoNCm9wdGlvbnMoc2NpcGVuPTk5OSkNCm9wdGlvbnModGlncmlzX2NsYXNzID0gInNmIikNCg0KIyBGdW5jdGlvbnMgYW5kIGRhdGEgZGlyZWN0b3J5DQoNCnNvdXJjZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3VyYmFuU3BhdGlhbC9QdWJsaWMtUG9saWN5LUFuYWx5dGljcy1MYW5kaW5nL21hc3Rlci9mdW5jdGlvbnMuciIpDQoNCiMgSW52b2tpbmcgY29sb3IgcGFsZXR0ZXMgdG8gYmUgdXNlZA0KDQpwYWxldHRlZSA8LSBjKCcjZDcxOTFjJywnI2ZkYWU2MScsJyNmZmZmYmYnLCcjYWJkOWU5JywnIzJjN2JiNicpDQoNCiMgUmVnaXN0ZXJpbmcgQVBJIEtleSB0byBiZSB1c2VkDQoNCmNlbnN1c19hcGlfa2V5KCdiZjJkNTA3NjUxYjVhNjIxZGJhZGQ0NDUzM2ZiNGYzZGVhYWIyNmJmJywgb3ZlcndyaXRlID0gVFJVRSkNCg0KYGBgDQoNCiMjIExvYWRpbmcgRGF0YQ0KDQpEYXRhIHNvdXJjZXMgdXNlZCBpbmNsdWRlIGNlbnN1cyBhbmQgb3BlbmRhdGEgcGhpbGx5IA0KDQpEYXRhIHByb3ZpZGVkIHdhcyBjbGVhbmVkIGFuZCBuZXcgdmFyaWFibGUgd2VyZSBjcmVhdGVkIA0KDQpgYGB7ciBSZWFkaW5nIERhdGEsIHJlc3VsdHM9ICdoaWRlJ30NCg0KIyBSZWFkaW5nIERhdGENCg0KZGF0YSA8LSANCiAgc3RfcmVhZCgiLi9kYXRhL3N0dWRlbnREYXRhLmdlb2pzb24iKSAlPiUNCiAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjI4NicpDQoNCiMgRHJvcHBpbmcgY29sdW1ucyB3aXRoIG5vIHZhbHVlcyBhbmQgZmlsdGVyaW5nIHZhbHVlcyB3aXRoaW4gUGhpbGFkZWxwaGlhDQoNCmRhdGEgPC0gIGRhdGEgJT4lIA0KICBzZWxlY3QoLWNyb3NzX3JlZmVyZW5jZSwgLWRhdGVfZXh0ZXJpb3JfY29uZGl0aW9uLCAtbWFpbGluZ19hZGRyZXNzXzIsIC1tYWlsaW5nX2NhcmVfb2YsIC11bmZpbmlzaGVkLCAtdXRpbGl0eSwgLWNhdGVnb3J5X2NvZGUsIC1jYXRlZ29yeV9jb2RlX2Rlc2NyaXB0aW9uLCAtZXhlbXB0X2xhbmQsIC1zZXBhcmF0ZV91dGlsaXRpZXMsIC1zZXdlciwgLSBzaXRlX3R5cGUsIC1ob3VzZV9leHRlbnNpb24sIC1zdHJlZXRfZGlyZWN0aW9uLCAtc3VmZml4LCAtZ2FyYWdlX3R5cGUsIC1nZW5lcmFsX2NvbnN0cnVjdGlvbiApJT4lIA0KICBmaWx0ZXIobWFpbGluZ19jaXR5X3N0YXRlID09ICJQSElMQURFTFBISUEgUEEiICkNCg0KYGBgDQoNCmBgYHtyIENsZWFuaW5nIERhdGEgYmFzZWQgb24gTWV0YWRhdGEsIHJlc3VsdHM9ICdoaWRlJ30NCg0KIyMgRmlsdGVyaW5nIG91dCA5IHJvd3Mgd2hlcmUgeWVhciBidWlsdCBpcyBub3QgZ2l2ZW4NCg0KZGF0YSA8LSAgZGF0YSAlPiUgDQogIGZpbHRlcih5ZWFyX2J1aWx0ID4gMCApDQoNCiMjIENhdGVnb3JpemluZyBpZiBhIEJhc2VtZW50IGlzIHByZXNlbnQNCg0KZGF0YSA8LSBkYXRhICU+JQ0KICBtdXRhdGUoQmFzZW1lbnRQcmVzZW50ID0gY2FzZV93aGVuKGJhc2VtZW50cyA9PSAnQScgfA0KICBiYXNlbWVudHMgPT0gJ0InIHwNCiAgYmFzZW1lbnRzID09ICdDJyB8DQogIGJhc2VtZW50cyA9PSAnRCcgfA0KICBiYXNlbWVudHMgPT0gJ0UnIHwNCiAgYmFzZW1lbnRzID09ICdGJyB8DQogIGJhc2VtZW50cyA9PSAnRycgfA0KICBiYXNlbWVudHMgPT0gJ0gnIHwNCiAgYmFzZW1lbnRzID09ICdJJyB8DQogIGJhc2VtZW50cyA9PSAnSicgfiAxLA0KICBiYXNlbWVudHMgPT0gJzAnIH4gMCkpDQoNCiMgQXNzaWduaW5nIHZhbHVlIG9mIDAgdG8gJ05BJyByb3dzIGJhc2VkIG9uIGRlc2NyaXB0aW9uIGluIG1ldGFkYXRhDQoNCmRhdGEkQmFzZW1lbnRQcmVzZW50W2lzLm5hKGRhdGEkQmFzZW1lbnRQcmVzZW50KV0gPC0gMA0KDQojIyBDYXRlZ29yaXppbmcgQmFzZW1lbnQgVHlwZQ0KDQpsaWJyYXJ5KGRwbHlyKQ0KDQpkYXRhIDwtIGRhdGEgJT4lDQogIG11dGF0ZShCYXNlbWVudFR5cGUgPSBjYXNlX3doZW4oYmFzZW1lbnRzID09ICdBJyB+ICdGdWxsIHNpemUgZmluaXNoZWQnLA0KICBiYXNlbWVudHMgPT0gJ0InIH4gJ0Z1bGwgc2l6ZSBzZW1pLWZpbmlzaGVkJywNCiAgYmFzZW1lbnRzID09ICdDJyB+ICdGdWxsIHNpemUgdW5maW5pc2hlZCcsDQogIGJhc2VtZW50cyA9PSAnRCcgfiAnRnVsbCBzaXplIHVua25vd24gZmluaXNoJywNCiAgYmFzZW1lbnRzID09ICdFJyB+ICdQYXJ0aWFsIHNpemUgZmluaXNoZWQnLA0KICBiYXNlbWVudHMgPT0gJ0YnIH4gJ1BhcnRpYWwgc2l6ZSBzZW1pLWZpbmlzaGVkJywNCiAgYmFzZW1lbnRzID09ICdHJyB+ICdQYXJ0aWFsIHNpemUgdW5maW5pc2hlZCcsDQogIGJhc2VtZW50cyA9PSAnSCcgfiAnUGFydGlhbCBzaXplIHVua25vd24gZmluaXNoJywNCiAgYmFzZW1lbnRzID09ICdJJyB+ICdVbmtub3duIHNpemUgZmluaXNoZWQnLA0KICBiYXNlbWVudHMgPT0gJ0onIH4gJ1Vua25vd24gc2l6ZSB1bmZpbmlzaGVkJywNCiAgYmFzZW1lbnRzID09ICcwJyB+ICdObyBiYXNlbWVudCcpKQ0KDQojIEFzc2lnbmluZyB2YWx1ZSBvZiAwIHRvICdOQScgcm93cyBiYXNlZCBvbiBkZXNjcmlwdGlvbiBpbiBtZXRhZGF0YQ0KDQpkYXRhJEJhc2VtZW50VHlwZVtpcy5uYShkYXRhJEJhc2VtZW50VHlwZSldIDwtICJObyBiYXNlbWVudCINCmRhdGEkYmFzZW1lbnRzW2lzLm5hKGRhdGEkYmFzZW1lbnRzKV0gPC0gMA0KDQojIyBDYXRlZ29yaXppbmcgYmFzZWQgb24gQ2VudHJhbCBBaXINCg0KZGF0YSRjZW50cmFsX2FpciA8LSBpZmVsc2UoZGF0YSRjZW50cmFsX2FpciA9PSAnWScsIDEsIDApDQoNCiMjIENhdGVnb3JpemluZyBiYXNlZCBvbiBFeHRlcmlvciBDb25kaXRpb24NCg0KZGF0YSA8LSBkYXRhICU+JQ0KICBtdXRhdGUoRXh0ZXJpb3JDb25kaXRpb25UeXBlID0gY2FzZV93aGVuKGV4dGVyaW9yX2NvbmRpdGlvbiA9PSAnMScgfA0KICBleHRlcmlvcl9jb25kaXRpb24gPT0gJzInIHwNCiAgZXh0ZXJpb3JfY29uZGl0aW9uID09ICczJyB+ICdHb29kIENvbmRpdGlvbicsDQogIGV4dGVyaW9yX2NvbmRpdGlvbiA9PSAnNCcgfA0KICBleHRlcmlvcl9jb25kaXRpb24gPT0gJzUnIH4gJ0F2ZXJhZ2UgQ29uZGl0aW9uJywNCiAgZXh0ZXJpb3JfY29uZGl0aW9uID09ICc2JyB+ICdCZWxvdyBBdmVyYWdlIENvbmRpdGlvbicsDQogIGV4dGVyaW9yX2NvbmRpdGlvbiA9PSAnNycgfiAnVmFjYW50IGFuZCBTZWFsZWQnKSkNCg0KIyBBc3NpZ25pbmcgdmFsdWUgdG8gc2luZ2xlICdOQScgcm93IGJhc2VkIG9uICdpbnRlcmlvcl9jb25zdHJ1Y3Rpb24nIHJhdGluZyBmb3Igc2FtZSByb3cNCg0KZGF0YSRFeHRlcmlvckNvbmRpdGlvblR5cGVbaXMubmEoZGF0YSRFeHRlcmlvckNvbmRpdGlvblR5cGUpXSA8LSAiR29vZCBDb25kaXRpb24iDQpkYXRhJGV4dGVyaW9yX2NvbmRpdGlvbltpcy5uYShkYXRhJGV4dGVyaW9yX2NvbmRpdGlvbildIDwtIDMNCg0KIyMgQ2F0ZWdvcml6aW5nIGJhc2VkIG9uIEZpcmVwbGFjZXMNCg0KIyBBc3NpZ25pbmcgdmFsdWUgb2YgMCB0byAnTkEnIHJvd3MgYmFzZWQgb24gZGVzY3JpcHRpb24gaW4gbWV0YWRhdGENCg0KZGF0YSRmaXJlcGxhY2VzW2lzLm5hKGRhdGEkZmlyZXBsYWNlcyldIDwtIDANCg0KIyMgQ2F0ZWdvcml6aW5nIGJhc2VkIG9uIEZ1ZWwgU291cmNlcw0KDQpkYXRhIDwtIGRhdGEgJT4lDQogIG11dGF0ZShGdWVsVHlwZSA9IGNhc2Vfd2hlbihmdWVsID09ICdBJyB+ICdOYXR1cmFsIEdhcyBQb3dlcmVkJywNCiAgZnVlbCA9PSAnQicgfiAnT2lsIFBvd2VyZWQnLA0KICBmdWVsID09ICdDJyB+ICdFbGVjdHJpYyBQb3dlcmVkJywNCiAgZnVlbCA9PSAnRScgfiAnU29sYXIgUG93ZXJlZCcsDQogIGZ1ZWwgPT0gJ0cnIH4gJ0Z1ZWwgU291cmNlIFVua25vd24nKSkNCg0KIyBBc3NpZ25pbmcgdmFsdWUgb2YgVW5rbm93bi9HIHRvICdOQScgcm93cyBiYXNlZCBvbiBkZXNjcmlwdGlvbiBpbiBtZXRhZGF0YQ0KDQpkYXRhJEZ1ZWxUeXBlW2lzLm5hKGRhdGEkRnVlbFR5cGUpXSA8LSAiRnVlbCBTb3VyY2UgVW5rbm93biINCmRhdGEkZnVlbFtpcy5uYShkYXRhJGZ1ZWwpXSA8LSAiRyINCg0KIyMgQ2F0ZWdvcml6aW5nIGJhc2VkIG9uIEdhcmFnZSBQcmVzZW5jZQ0KDQpkYXRhIDwtIGRhdGEgJT4lDQogIG11dGF0ZShHYXJhZ2VQcmVzZW50ID0gY2FzZV93aGVuKGdhcmFnZV9zcGFjZXMgPT0gJzAnIH4gMCwNCiAgZ2FyYWdlX3NwYWNlcyA9PSAnMScgfA0KICBnYXJhZ2Vfc3BhY2VzID09ICcyJyB8DQogIGdhcmFnZV9zcGFjZXMgPT0gJzMnIH4gMSkpDQoNCiMgQXNzaWduaW5nIHZhbHVlIG9mIDAgdG8gJ05BJyByb3dzIGJhc2VkIG9uIGRlc2NyaXB0aW9uIGluIG1ldGFkYXRhDQoNCmRhdGEkR2FyYWdlUHJlc2VudFtpcy5uYShkYXRhJEdhcmFnZVByZXNlbnQpXSA8LSAwDQpkYXRhJGdhcmFnZV9zcGFjZXNbaXMubmEoZGF0YSRnYXJhZ2Vfc3BhY2VzKV0gPC0gMA0KDQojIyBDYXRlZ29yaXppbmcgYmFzZWQgb24gR2FyYWdlIFR5cGVzDQoNCmRhdGEgPC0gZGF0YSAlPiUNCiAgbXV0YXRlKEdhcmFnZVR5cGUgPSBjYXNlX3doZW4oZ2FyYWdlX3NwYWNlcyA9PSAnMCcgfiAnTm8gR2FyYWdlJywNCiAgZ2FyYWdlX3NwYWNlcyA9PSAnMScgfiAnU2luZ2xlIEdhcmFnZScsDQogIGdhcmFnZV9zcGFjZXMgPT0gJzInIHwNCiAgZ2FyYWdlX3NwYWNlcyA9PSAnMycgfiAnTXVsdGlwbGUgR2FyYWdlcycpKQ0KDQojIEFzc2lnbmluZyB2YWx1ZSBvZiAwIHRvICdOQScgcm93cyBiYXNlZCBvbiBkZXNjcmlwdGlvbiBpbiBtZXRhZGF0YQ0KDQpkYXRhJEdhcmFnZVR5cGVbaXMubmEoZGF0YSRHYXJhZ2VUeXBlKV0gPC0gIk5vIEdhcmFnZSINCmRhdGEkZ2FyYWdlX3NwYWNlc1tpcy5uYShkYXRhJGdhcmFnZV9zcGFjZXMpXSA8LSAwDQoNCiMjIENhdGVnb3JpemluZyBiYXNlZCBvbiBFeHRlcmlvciBDb25kaXRpb24NCg0KZGF0YSA8LSBkYXRhICU+JQ0KICBtdXRhdGUoSW50ZXJpb3JDb25kaXRpb25UeXBlID0gY2FzZV93aGVuKCBpbnRlcmlvcl9jb25kaXRpb24gPT0gJzAnIHwNCiAgaW50ZXJpb3JfY29uZGl0aW9uID09ICcxJyB8DQogIGludGVyaW9yX2NvbmRpdGlvbiA9PSAnMicgfA0KICBpbnRlcmlvcl9jb25kaXRpb24gPT0gJzMnIH4gJ0dvb2QgQ29uZGl0aW9uJywNCiAgaW50ZXJpb3JfY29uZGl0aW9uID09ICc0JyB+ICdBdmVyYWdlIENvbmRpdGlvbicsDQogIGludGVyaW9yX2NvbmRpdGlvbiA9PSAnNScgfiAnQmVsb3cgQXZlcmFnZSBDb25kaXRpb24nLA0KICBpbnRlcmlvcl9jb25kaXRpb24gPT0gJzYnIHwNCiAgaW50ZXJpb3JfY29uZGl0aW9uID09ICc3JyB+ICdWYWNhbnQgYW5kL29yIFNlYWxlZCcpKQ0KDQojIEFzc2lnbmluZyB2YWx1ZXMgdG8gMCBvciAnTkEnIHJvd3MgYmFzZWQgb24gZXh0ZXJpb3IgY29uZGl0aW9uIHNpbmNlIGludGVyaW9yIGFuZCBleHRlcmlvciBjb25kaXRpb25zIGFyZSBlcXVhbCBpbiBhbG1vc3QgYWxsIGNhc2VzDQoNCmRhdGEkSW50ZXJpb3JDb25kaXRpb25UeXBlW2lzLm5hKGRhdGEkSW50ZXJpb3JDb25kaXRpb25UeXBlKV0gPC0gYyhkYXRhJEV4dGVyaW9yQ29uZGl0aW9uVHlwZSkNCg0KIyMgQ2F0ZWdvcml6aW5nIGJhc2VkIG9uIFZpZXcgVHlwZQ0KDQpkYXRhIDwtIGRhdGEgJT4lDQogIG11dGF0ZShWaWV3VHlwZSA9IGNhc2Vfd2hlbih2aWV3X3R5cGUgPT0gJzAnIH4gJ05hdHVyZSBvZiBWaWV3IFVua25vd24nLA0KICB2aWV3X3R5cGUgPT0gJ0knIH4gJ1R5cGljYWwgVmlldycsDQogIHZpZXdfdHlwZSA9PSAnQScgfiAnU2t5bGluZSBWaWV3JywNCiAgdmlld190eXBlID09ICdCJyB+ICdSaXZlciBWaWV3JywNCiAgdmlld190eXBlID09ICdDJyB+ICdQYXJrIFZpZXcnLA0KICB2aWV3X3R5cGUgPT0gJ0QnIH4gJ0NvbW1lcmNpYWwgQXJlYSBWaWV3JywNCiAgdmlld190eXBlID09ICdFJyB+ICdJbmR1c3RyaWFsIEFyZWEgVmlldycsDQogIHZpZXdfdHlwZSA9PSAnSCcgfiAnVmlldyBvZiBMYW5kbWFyaycpKQ0KDQojIEFzc2lnbmluZyB2YWx1ZSB0byBOQSByb3dzIHRvIG5hdHVyZSBvZiB2aWV3IHVua25vd24NCg0KZGF0YSRWaWV3VHlwZVtpcy5uYShkYXRhJFZpZXdUeXBlKV0gPC0gIk5hdHVyZSBvZiBWaWV3IFVua25vd24iDQpkYXRhJHZpZXdfdHlwZVtpcy5uYShkYXRhJHZpZXdfdHlwZSldIDwtIDANCg0KIyMgQ2F0ZWdvcml6aW5nIGJhc2VkIG9uIFRvcG9ncmFwaHkgVHlwZQ0KDQpkYXRhIDwtIGRhdGEgJT4lDQogIG11dGF0ZShUb3BvZ3JhcGh5VHlwZSA9IGNhc2Vfd2hlbih0b3BvZ3JhcGh5ID09ICdBJyB+ICdBYm92ZSBTdHJlZXQgTGV2ZWwgVG9wb2dyYXBoeScsDQogIHRvcG9ncmFwaHkgPT0gJ0InIH4gJ0JlbG93IFN0cmVldCBMZXZlbCBUb3BvZ3JhcGh5JywNCiAgdG9wb2dyYXBoeSA9PSAnQycgfiAnRmxvb2QgUGxhaW4gVG9wb2dyYXBoeScsDQogIHRvcG9ncmFwaHkgPT0gJ0QnIH4gJ1JvY2t5IFRvcG9ncmFwaHknLA0KICB0b3BvZ3JhcGh5ID09ICdFJyB+ICdPdGhlciBUb3BvZ3JhcGh5JywNCiAgdG9wb2dyYXBoeSA9PSAnRicgfiAnTGV2ZWwgVG9wb2dyYXBoeScpKQ0KDQojIEFzc2lnbmluZyB2YWx1ZSB0byBOQSByb3dzIHRvIG5hdHVyZSBvZiB2aWV3IHVua25vd24NCg0KZGF0YSRUb3BvZ3JhcGh5VHlwZVtpcy5uYShkYXRhJFRvcG9ncmFwaHlUeXBlKV0gPC0gIlRvcG9ncmFwaHkgVW5rbm93biINCmRhdGEkdG9wb2dyYXBoeVtpcy5uYShkYXRhJHRvcG9ncmFwaHkpXSA8LSAwDQoNCiMjIENhdGVnb3JpemluZyBiYXNlZCBvbiBQYXJjZWwgVHlwZQ0KDQpkYXRhIDwtIGRhdGEgJT4lDQogIG11dGF0ZShQYXJjZWxUeXBlID0gY2FzZV93aGVuKHBhcmNlbF9zaGFwZSA9PSAnQScgfiAnSXJyZWd1bGFyIFBhcmNlbCcsDQogIHBhcmNlbF9zaGFwZSA9PSAnQicgfiAnR3Jvc3NseSBJcnJlZ3VsYXIgUGFyY2VsJywNCiAgcGFyY2VsX3NoYXBlID09ICdDJyB+ICdUcmlhbmd1bGFyIFBhcmNlbCcsDQogIHBhcmNlbF9zaGFwZSA9PSAnRCcgfiAnTG9uZyBOYXJyb3cgUGFyY2VsJywNCiAgcGFyY2VsX3NoYXBlID09ICdFJyB+ICdSZWN0YW5ndWxhciBQYXJjZWwnKSkNCg0KIyBBc3NpZ25pbmcgdmFsdWUgdG8gTkEgcm93cyB0byBuYXR1cmUgb2YgdmlldyB1bmtub3duDQoNCmRhdGEkUGFyY2VsVHlwZVtpcy5uYShkYXRhJFBhcmNlbFR5cGUpXSA8LSAiUGFyY2VsIFR5cGUgVW5rbm93biINCmRhdGEkcGFyY2VsX3NoYXBlW2lzLm5hKGRhdGEkcGFyY2VsX3NoYXBlKV0gPC0gMA0KDQoNCmBgYA0KYGBge3IgRHJvcHBpbmcgY29sdW1ucyB3aXRoIHNhbGUgcHJpY2UwLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCg0KZGF0YSA8LSAgZGF0YSAlPiUgDQogIGZpbHRlcihzYWxlX3ByaWNlID4gMCkNCg0KYGBgDQoNCg0KYGBge3IgSW1wdXRpbmcgdmFsdWVzIGZvciBtaXNzaW5nIHZhbHVlcyBvZiBudW1iZXIgb2YgYmVkcm9vbXMsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KDQojIyBJbXB1dGluZyB2YWx1ZXMgZm9yIG1pc3NpbmcgdmFsdWVzIG9mIG51bWJlciBvZiBiZWRyb29tcyBiYXNlZCBvbiB0b3RhbCBsaXZhYmxlIGFyZWENCg0KIyBEcm9wcGluZyAzMiB2YWx1ZXMgb2YgdG90YWwgbGl2YWJsZSBhcmVhIHdoaWNoIGFyZSAwIGZvciBiZXR0ZXIgcHJlZGljdGlvbg0KDQpkYXRhIDwtICBkYXRhICU+JSANCiAgZmlsdGVyKHRvdGFsX2xpdmFibGVfYXJlYSA+IDAgKQ0KDQojIFN0ZXAgMSAtIENyZWF0aW5nIGFuIGluZGV4IG9mIDAgYW5kIDEgdmFsdWVzIGZvciByb3dzIHRoYXQgaGF2ZSB2YWx1ZXMgZm9yIG51bWJlciBvZiBiYXRocm9vbXMgYW5kIHJvd3MgdGhhdCBkbyBub3QNCg0KZGF0YSA8LSBkYXRhICU+JQ0KICBtdXRhdGUoQmVkcm9vbUluZGV4ID0gY2FzZV93aGVuKG51bWJlcl9vZl9iZWRyb29tcyA+PSAxIH4gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXJfb2ZfYmVkcm9vbXMgPCAxIH4gMCkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpkYXRhJEJlZHJvb21JbmRleFtpcy5uYShkYXRhJEJlZHJvb21JbmRleCldIDwtIDANCg0KIyBTdGVwIDIgLSBDcmVhdGluZyBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHJlbGF0aW5nIG51bWJlciBvZiBiZWRyb29tcyBhbmQgdG90YWwgbGl2ZWFibGUgYXJlYQ0KDQpsbShudW1iZXJfb2ZfYmVkcm9vbXMgfiB0b3RhbF9saXZhYmxlX2FyZWEsIGRhdGE9ZGF0YSkNCg0KIyBTdGVwIDMgLSBJbXB1dGluZyBuZXcgdmFsdWVzIGZvciBtaXNzaW5nIHZhbHVlcyBvZiBudW1iZXIgb2YgYmVkcm9vbXMgdXNpbmcgcmVncmVzc2lvbiByZXN1bHRzDQoNCmZvcihpIGluIDE6bnJvdyhkYXRhKSkNCnsNCiAgaWYgKGRhdGEkQmVkcm9vbUluZGV4W2ldID09IDApDQogIHsNCiAgICBkYXRhJG51bWJlcl9vZl9iZWRyb29tc1tpXSA9IDIuMTYwNTY1MCArIDAuMDAwMzA1NypkYXRhJHRvdGFsX2xpdmFibGVfYXJlYVtpXQ0KICB9DQogIH0NCg0KZGF0YSRudW1iZXJfb2ZfYmVkcm9vbXMgPC0gcm91bmQoZGF0YSRudW1iZXJfb2ZfYmVkcm9vbXMsIGRpZ2l0cz0wKQ0KDQpgYGANCg0KYGBge3IgSW1wdXRpbmcgdmFsdWVzIGZvciBtaXNzaW5nIHZhbHVlcyBvZiBudW1iZXIgb2Ygcm9vbXMsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KDQojIyBJbXB1dGluZyB2YWx1ZXMgZm9yIG1pc3NpbmcgdmFsdWVzIG9mIG51bWJlciBvZiByb29tcyBiYXNlZCBvbiB0b3RhbCBsaXZhYmxlIGFyZWENCg0KIyBTdGVwIDEgLSBDcmVhdGluZyBhbiBpbmRleCBvZiAwIGFuZCAxIHZhbHVlcyBmb3Igcm93cyB0aGF0IGhhdmUgdmFsdWVzIGZvciBudW1iZXIgb2Ygcm9vbXMgYW5kIHJvd3MgdGhhdCBkbyBub3QNCg0KZGF0YSA8LSBkYXRhICU+JQ0KICBtdXRhdGUoUm9vbUluZGV4ID0gY2FzZV93aGVuKG51bWJlcl9vZl9yb29tcyA+PSAxIH4gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXJfb2Zfcm9vbXMgPCAxIH4gMCkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpkYXRhJFJvb21JbmRleFtpcy5uYShkYXRhJFJvb21JbmRleCldIDwtIDANCg0KIyBTdGVwIDIgLSBDcmVhdGluZyBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHJlbGF0aW5nIG51bWJlciBvZiByb29tcyBhbmQgdG90YWwgbGl2YWJsZSBhcmVhDQoNCmxtKG51bWJlcl9vZl9yb29tcyB+IHRvdGFsX2xpdmFibGVfYXJlYSwgZGF0YT1kYXRhKQ0KDQojIFN0ZXAgMyAtIEltcHV0aW5nIG5ldyB2YWx1ZXMgZm9yIG1pc3NpbmcgdmFsdWVzIG9mIG51bWJlciBvZiByb29tcyB1c2luZyByZWdyZXNzaW9uIHJlc3VsdHMNCg0KZm9yKGkgaW4gMTpucm93KGRhdGEpKQ0Kew0KICBpZiAoZGF0YSRSb29tSW5kZXhbaV0gPT0gMCkNCiAgew0KICAgIGRhdGEkbnVtYmVyX29mX3Jvb21zW2ldID0gNC4zMTY1MDMgKyAwLjAwMTMxOSpkYXRhJHRvdGFsX2xpdmFibGVfYXJlYVtpXQ0KICB9DQogIH0NCg0KZGF0YSRudW1iZXJfb2Zfcm9vbXMgPC0gcm91bmQoZGF0YSRudW1iZXJfb2Zfcm9vbXMsIGRpZ2l0cz0wKQ0KDQpgYGANCg0KYGBge3IgSW1wdXRpbmcgdmFsdWVzIGZvciBtaXNzaW5nIHZhbHVlcyBvZiBudW1iZXIgb2YgYmF0aHJvb21zLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCg0KIyMgSW1wdXRpbmcgdmFsdWVzIGZvciBtaXNzaW5nIHZhbHVlcyBvZiBudW1iZXIgb2YgYmF0aHJvb21zIGJhc2VkIG9uIHRvdGFsIGxpdmFibGUgYXJlYQ0KDQojIFN0ZXAgMSAtIENyZWF0aW5nIGFuIGluZGV4IG9mIDAgYW5kIDEgdmFsdWVzIGZvciByb3dzIHRoYXQgaGF2ZSB2YWx1ZXMgZm9yIG51bWJlciBvZiBiYXRocm9vbXMgYW5kIHJvd3MgdGhhdCBkbyBub3QNCg0KZGF0YSA8LSBkYXRhICU+JQ0KICBtdXRhdGUoQmF0aHJvb21JbmRleCA9IGNhc2Vfd2hlbihudW1iZXJfb2ZfYmF0aHJvb21zID49IDEgfiAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlcl9vZl9iYXRocm9vbXMgPCAxIH4gMCkpDQoNCmRhdGEkQmF0aHJvb21JbmRleFtpcy5uYShkYXRhJEJhdGhyb29tSW5kZXgpXSA8LSAwDQoNCiMgU3RlcCAyIC0gQ3JlYXRpbmcgYSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCByZWxhdGluZyBudW1iZXIgb2YgYmF0aHJvb21zIGFuZCAgdG90YWwgbGl2YWJsZSBhcmVhDQoNCmxtKG51bWJlcl9vZl9iYXRocm9vbXMgfiB0b3RhbF9saXZhYmxlX2FyZWEsIGRhdGE9ZGF0YSkNCg0KIyBTdGVwIDMgLSBJbXB1dGluZyBuZXcgdmFsdWVzIGZvciBtaXNzaW5nIHZhbHVlcyBvZiBudW1iZXIgb2YgYmF0aHJvb21zIHVzaW5nIHJlZ3Jlc3Npb24gcmVzdWx0cw0KDQpmb3IoaSBpbiAxOm5yb3coZGF0YSkpDQp7DQogIGlmIChkYXRhJEJhdGhyb29tSW5kZXhbaV0gPT0gMCkNCiAgew0KICAgIGRhdGEkbnVtYmVyX29mX2JhdGhyb29tc1tpXSA9IDAuNjQyNjMxMCArIDAuMDAwMzI4MypkYXRhJHRvdGFsX2xpdmFibGVfYXJlYVtpXQ0KICB9DQogIH0NCg0KZGF0YSRudW1iZXJfb2ZfYmF0aHJvb21zIDwtIHJvdW5kKGRhdGEkbnVtYmVyX29mX2JhdGhyb29tcywgZGlnaXRzPTApDQoNCmBgYA0KDQpgYGB7ciBQcmljZS9TcWZ0YSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQoNCmRhdGEgPC0gDQogIGRhdGEgJT4lDQogIG11dGF0ZShQcmljZVBlclNxZnQgPSAoc2FsZV9wcmljZS90b3RhbF9saXZhYmxlX2FyZWEpKQ0KDQpgYGANCg0KDQpgYGB7ciBmaWx0ZXIgZGF0YSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpkYXRhX29nIDwtIGRhdGENCg0KZGF0YSA8LSBkYXRhICU+JSANCiAgc2VsZWN0KG9iamVjdGlkLCBhc3Nlc3NtZW50X2RhdGUsIHllYXJfYnVpbHQsIGJ1aWxkaW5nX2NvZGUsIGJ1aWxkaW5nX2NvZGVfZGVzY3JpcHRpb24sIHBpbiwgYnVpbGRpbmdfY29kZV9uZXcsIGJ1aWxkaW5nX2NvZGVfZGVzY3JpcHRpb25fbmV3LCAgY2Vuc3VzX3RyYWN0LCBnZW9ncmFwaGljX3dhcmQsIHpvbmluZywgbG9jYXRpb24sIHN0cmVldF9uYW1lLCBzdHJlZXRfY29kZSwgc3RyZWV0X2Rlc2lnbmF0aW9uLCB6aXBfY29kZSwgaG91c2VfbnVtYmVyLCBkZXB0aCwgZnJvbnRhZ2UsIGNlbnRyYWxfYWlyLCBmaXJlcGxhY2VzLCBmdWVsLCBGdWVsVHlwZSwgYmFzZW1lbnRzLCBCYXNlbWVudFByZXNlbnQsIEJhc2VtZW50VHlwZSwgZ2FyYWdlX3NwYWNlcywgR2FyYWdlUHJlc2VudCwgR2FyYWdlVHlwZSwgZXh0ZXJpb3JfY29uZGl0aW9uLCBFeHRlcmlvckNvbmRpdGlvblR5cGUsIGludGVyaW9yX2NvbmRpdGlvbiwgSW50ZXJpb3JDb25kaXRpb25UeXBlLCBudW1iZXJfb2ZfYmF0aHJvb21zLCBudW1iZXJfb2ZfYmVkcm9vbXMsIG51bWJlcl9vZl9yb29tcywgbnVtYmVyX3N0b3JpZXMsIHRvdGFsX2xpdmFibGVfYXJlYSwgdmlld190eXBlLCBWaWV3VHlwZSwgdG9wb2dyYXBoeSwgVG9wb2dyYXBoeVR5cGUsIHBhcmNlbF9zaGFwZSwgUGFyY2VsVHlwZSwgc2FsZV9kYXRlLCBzYWxlX3llYXIsIHNhbGVfcHJpY2UsIFByaWNlUGVyU3FmdCwgbXVzYUlELCB0b1ByZWRpY3QsIGdlb21ldHJ5ICkNCmBgYA0KDQpgYGB7ciBmaWx0ZXIgb3V0bGllcnMsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KDQpkYXRhIDwtICBkYXRhICU+JSANCiAgZmlsdGVyKG51bWJlcl9vZl9iZWRyb29tcyA8MTAsIG51bWJlcl9vZl9yb29tczwxNSwgKChudW1iZXJfb2ZfYmF0aHJvb21zK251bWJlcl9vZl9iZWRyb29tcykgPCBudW1iZXJfb2Zfcm9vbXMpLCBQcmljZVBlclNxZnQ8MTcwMCwgdG90YWxfbGl2YWJsZV9hcmVhPDEwMDAwKQ0KDQpgYGANCg0KDQojIyBDZW5zdXMgRGF0YQ0KDQpUaGUgeWVhcnMgY2hvc2VuIGZvciBhbmFseXNpcyBhcmUgMjAyMSBiZWNhdXNlIGNvdmlkIHJlY292ZXJ5IG1vc3QgcmVjZW50IHRvIG1rYWUgbW9yZSBhY2NjdXJhdGUgcHJlZGljaXRvbnMNCg0KDQpUaGUgdmFyaWFibGVzIGNob3NlbiBmb3IgdGhpcyBhbmFseXNpcyBpbmNsdWRlOiANCg0KMS4gaW5jb21lIGJlY2F1c2UgLSANCg0KMi4gDQoNCjMuIA0KDQoNCmBgYHtyIGNlbnN1cywgY2FjaGU9VFJVRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0gJ2hpZGUnfQ0KDQphY3NfdmFyaWFibGVfbGlzdC4yMDIxIDwtIGxvYWRfdmFyaWFibGVzKDIwMjEsICN5ZWFyDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhY3M1IiwgI2ZpdmUgeWVhciBBQ1MgZXN0aW1hdGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhY2hlID0gVFJVRSkNCiMgMjAyMSwgQQ0KDQojIFJldHJpZXZlIEFDUyBkYXRhIGZvciBQaGlsYWRlbHBoaWEgdHJhY3RzIGluIDIwMjANCnRyYWN0czIxIDwtIGdldF9hY3MoDQogIGdlb2dyYXBoeSA9ICJ0cmFjdCIsDQogIHZhcmlhYmxlcyA9IGMoDQogICAgIkIwMTAwM18wMDEiLCAgICMgVG90YWwgUG9wdWxhdGlvbg0KICAgICJCMTkwMTNfMDAxIiwgICAjIE1lZGlhbiBIb3VzZWhvbGQgSW5jb21lDQogICAgIkIyNTA1OF8wMDEiLCAgICMgTWVkaWFuIFJlbnQNCiAgICAiQjI1MDA4XzAwMiIsICAgIyBPd25lci1PY2N1cGllZCBVbml0cw0KICAgICJCMjUwMDhfMDAzIiwgICAjIFJlbnRlci1PY2N1cGllZCBVbml0cw0KICAgICJCMDcwMDFfMDMyIiwgICAjIFNhbWUgSG91c2UgNzUgWWVhcnMgQWdvDQogICAgIkIwNzAwMV8wMTciLCAgICMgU2FtZSBIb3VzZSAxIFllYXIgQWdvDQogICAgIkIyNTA4OF8wMDMiLCAgICMgTWVkaWFuIFNlbGVjdGVkIE1vbnRobHkgT3duZXIgQ29zdHMgKGhvbWVzIHdpdGhvdXQgYSBtb3J0Z2FnZSkNCiAgICAiQjI1MDg4XzAwMiIsICAgIyBNZWRpYW4gU2VsZWN0ZWQgTW9udGhseSBPd25lciBDb3N0cyAoaG9tZXMgd2l0aCBhIG1vcnRnYWdlKQ0KICAgICJCMjUwNjRfMDAxIiwgICAjIE1lZGlhbiBHcm9zcyBSZW50IChyZW50IGFuZCB1dGlsaXRpZXMpDQogICAgIkIyNTExN18wMDEiLCAgICMgUGVyY2VudGFnZSBvZiBIb3VzaW5nIFVuaXRzIHdpdGggaGVhdA0KICAgICJCMTUwMDNfMDIyIiwgICAjIEVkdWNhdGlvbmFsIEF0dGFpbm1lbnQ6IEJhY2hlbG9yJ3MgRGVncmVlDQogICAgIkIxNzAwMV8wMDIiLCAgICMgUGVyY2VudGFnZSBvZiBQb3B1bGF0aW9uIEJlbG93IHRoZSBQb3ZlcnR5IExldmVsDQogICAgIkIyODAwMl8wMDQiLCAgICMgUGVyY2VudGFnZSBvZiBIb3VzaW5nIFVuaXRzIHdpdGggSGlnaC1TcGVlZCBJbnRlcm5ldA0KICAgICJCMjUwNDRfMDAzIiwgICAjIFBlcmNlbnRhZ2Ugb2YgSG91c2luZyBVbml0cyB3aXRoIE5vIFZlaGljbGUgQXZhaWxhYmxlDQogICAgIkIwMjAwMV8wMDIiLCAgICMgUmFjZSBhbmQgRXRobmljaXR5OiBXaGl0ZSBBbG9uZQ0KICAgICJCMDIwMDFfMDAzIiwgICAjIFJhY2UgYW5kIEV0aG5pY2l0eTogQmxhY2sgb3IgQWZyaWNhbiBBbWVyaWNhbiBBbG9uZQ0KICAgICJCMDMwMDFfMDAzIiAgICAjIEhpc3BhbmljIG9yIExhdGlubyBPcmlnaW4gb2YgUG9wdWxhdGlvbg0KICApLA0KICB5ZWFyID0gMjAyMSwNCiAgc3RhdGUgPSAiUEEiLA0KICBjb3VudHkgPSAiUGhpbGFkZWxwaGlhIiwNCiAgZ2VvbWV0cnkgPSBUUlVFLA0KICBvdXRwdXQgPSAid2lkZSINCiklPiUNCiAgc2VsZWN0KC1OQU1FLCAtZW5kc193aXRoKCJNIikpICU+JQ0KICByZW5hbWUodG90YWxwb3AgPSBCMDEwMDNfMDAxRSwgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFRvdGFsIFBvcHVsYXRpb24NCiAgICAgICAgIG1lZF9pbmNvbWUgPSBCMTkwMTNfMDAxRSwgICAgICAgICAgICAgICAgICAgICAgICAgIyBNZWRpYW4gSG91c2Vob2xkIEluY29tZQ0KICAgICAgICAgbWVkX3JlbnQgPSBCMjUwNThfMDAxRSwgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE1lZGlhbiBSZW50DQogICAgICAgICBvd25lcl91bml0cyA9IEIyNTAwOF8wMDJFLCAgICAgICAgICAgICAgICAgICAgICAgICMgT3duZXItT2NjdXBpZWQgVW5pdHMNCiAgICAgICAgIHJlbnRlcl91bml0cyA9IEIyNTAwOF8wMDNFLCAgICAgICAgICAgICAgICAgICAgICAgIyBSZW50ZXItT2NjdXBpZWQgVW5pdHMNCiAgICAgICAgIHNhbWVfaG91c2VfNzUgPSBCMDcwMDFfMDMyRSwgICAgICAgICAgICAgICAgICAgICAgIyBTYW1lIEhvdXNlIDc1IFllYXJzIEFnbw0KICAgICAgICAgc2FtZV9ob3VzZV8xID0gQjA3MDAxXzAxN0UsICAgICAgICAgICAgICAgICAgICAgICAjIFNhbWUgSG91c2UgMSBZZWFyIEFnbw0KICAgICAgICAgbW9udGhseV9jb3N0c19ub19tb3J0Z2FnZSA9IEIyNTA4OF8wMDNFLCAgICAgICAgICAjIE1lZGlhbiBTZWxlY3RlZCBNb250aGx5IE93bmVyIENvc3RzIChob21lcyB3aXRob3V0IGEgbW9ydGdhZ2UpDQogICAgICAgICBtb250aGx5X2Nvc3RzX3dpdGhfbW9ydGdhZ2UgPSBCMjUwODhfMDAyRSwgICAgICAgICMgTWVkaWFuIFNlbGVjdGVkIE1vbnRobHkgT3duZXIgQ29zdHMgKGhvbWVzIHdpdGggYSBtb3J0Z2FnZSkNCiAgICAgICAgIG1lZF9ncm9zc19yZW50ID0gQjI1MDY0XzAwMUUsICAgICAgICAgICAgICAgICAgICAgIyBNZWRpYW4gR3Jvc3MgUmVudCAocmVudCBhbmQgdXRpbGl0aWVzKQ0KICAgICAgICAgaG91c2luZ191bml0c193aXRoX2hlYXQgPSBCMjUxMTdfMDAxRSwgICAgICAgICAgICAjIFBlcmNlbnRhZ2Ugb2YgSG91c2luZyBVbml0cyB3aXRoIGhlYXQNCiAgICAgICAgIGVkdV9iYWNoZWxvcnMgPSBCMTUwMDNfMDIyRSwgICAgICAgICAgICAgICAgICAgICAgIyBFZHVjYXRpb25hbCBBdHRhaW5tZW50OiBCYWNoZWxvcidzIERlZ3JlZQ0KICAgICAgICAgcG9wX2JlbG93X3BvdmVydHkgPSBCMTcwMDFfMDAyRSwgICAgICAgICAgICAgICAgICAjIFBlcmNlbnRhZ2Ugb2YgUG9wdWxhdGlvbiBCZWxvdyB0aGUgUG92ZXJ0eSBMZXZlbA0KICAgICAgICAgaG91c2luZ191bml0c19oaWdoX3NwZWVkX2ludGVybmV0ID0gQjI4MDAyXzAwNEUsICAjIFBlcmNlbnRhZ2Ugb2YgSG91c2luZyBVbml0cyB3aXRoIEhpZ2gtU3BlZWQgSW50ZXJuZXQNCiAgICAgICAgIGhvdXNpbmdfdW5pdHNfbm9fdmVoaWNsZSA9IEIyNTA0NF8wMDNFLCAgICAgICAgICAgIyBQZXJjZW50YWdlIG9mIEhvdXNpbmcgVW5pdHMgd2l0aCBObyBWZWhpY2xlIEF2YWlsYWJsZQ0KICAgICAgICAgcmFjZV93aGl0ZSA9IEIwMjAwMV8wMDJFLCAgICAgICAgICAgICAgICAgICAgICAgICAjIFJhY2UgYW5kIEV0aG5pY2l0eTogV2hpdGUgQWxvbmUNCiAgICAgICAgIHJhY2VfYmxhY2sgPSBCMDIwMDFfMDAzRSwgICAgICAgICAgICAgICAgICAgICAgICAgIyBSYWNlIGFuZCBFdGhuaWNpdHk6IEJsYWNrIG9yIEFmcmljYW4gQW1lcmljYW4gQWxvbmUNCiAgICAgICAgIGhpc3BhbmljX2xhdGlubyA9IEIwMzAwMV8wMDNFICAgICAgICAgICAgICAgICAgICAgIyBSYWNlIGFuZCBFdGhuaWNpdHk6IEhpc3BhbmljIG9yIExhdGlubw0KICAgICAgICAgKQ0KDQojIFRyYW5zZm9ybSB0aGUgZGF0YSB0byBFU1JJOjEwMjcyOCBwcm9qZWN0aW9uDQp0cmFjdHMyMSA8LSB0cmFjdHMyMSAlPiUgc3RfdHJhbnNmb3JtKHN0X2NycyhkYXRhKSkNCg0KYGBgDQoNCiMjIE9wZW4gRGF0YSBwaGlsbHkgDQoNCnByaXZhdGUgc2Nob29scyBwcm94aW1pdHksIHBhcmtzIGFuZCBsYW5kbWFya3MsIGZsb29kcGxhaW5zLCBkYWlseSBhcnJlc3RzLCBsaXR0ZXIgc2NvcmUsIGhlYXQgaW5kZXgNCg0KcGhpbGx5IHJpc2luZyBib3VuZGFyaWVzPyANCg0KYGBge3IgQWRkaW5nIERhdGEsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9ICdoaWRlJ30NCg0KIyBBZGRpbmcgZGF0YSBvbiBQaGlsYWRlbHBoaWEgU2Nob29scw0KDQpQaGlsbHlTY2hvb2xzIDwtDQogICBzdF9yZWFkKCIuL2RhdGEvU2Nob29scy5nZW9qc29uIikgJT4lDQogIGZpbHRlcihUWVBFX1NQRUNJRklDID09ICJQUklWQVRFIikgJT4lDQogIHN0X3RyYW5zZm9ybShzdF9jcnModHJhY3RzMjEpKQ0KDQojIE1hcHBpbmcgbmVhcmVzdCBzY2hvb2xzIA0KDQpuZWFyZXN0X2Z0cyA8LSBzZjo6c3RfbmVhcmVzdF9mZWF0dXJlKGRhdGEsIFBoaWxseVNjaG9vbHMpDQoNCiMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzDQoNCnggPC0gcnNnZW86OmFzX3JzZ2VvKGRhdGEpDQp5IDwtIHJzZ2VvOjphc19yc2dlbyhQaGlsbHlTY2hvb2xzKQ0KDQojIENhbGN1bGF0aW5nIGRpc3RhbmNlDQoNCmRhdGEkZGlzdF90b19wdnRfc2Nob29scyA8LSByc2dlbzo6ZGlzdGFuY2VfZXVjbGlkZWFuX3BhaXJ3aXNlKHgsIHlbbmVhcmVzdF9mdHNdKQ0KDQojIEFkZGluZyBkYXRhIG9uIFBoaWxhZGVscGhpYSBsYW5kbWFya3MgDQoNClBoaWxseUxhbmRtYXJrcyA8LQ0KIHN0X3JlYWQoImh0dHBzOi8vc2VydmljZXMuYXJjZ2lzLmNvbS9mTGVHamI3dTR1WHFlRjlxL2FyY2dpcy9yZXN0L3NlcnZpY2VzL0xhbmRtYXJrX1BvaW50cy9GZWF0dXJlU2VydmVyLzAvcXVlcnk/b3V0RmllbGRzPSomd2hlcmU9MSUzRDEmZj1nZW9qc29uIiklPiUNCiAgc3RfdHJhbnNmb3JtKHN0X2Nycyh0cmFjdHMyMSkpDQoNCiMgTWFwcGluZyBuZWFyZXN0IGxhbmRtYXJrcyANCg0KbmVhcmVzdF9mdHMgPC0gc2Y6OnN0X25lYXJlc3RfZmVhdHVyZShkYXRhLCBQaGlsbHlMYW5kbWFya3MpDQoNCiMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzDQoNCnggPC0gcnNnZW86OmFzX3JzZ2VvKGRhdGEpDQp5IDwtIHJzZ2VvOjphc19yc2dlbyhQaGlsbHlMYW5kbWFya3MpDQoNCiMgQ2FsY3VsYXRpbmcgZGlzdGFuY2UNCg0KZGF0YSRkaXN0X3RvX2xhbmRtYXJrcyA8LSByc2dlbzo6ZGlzdGFuY2VfZXVjbGlkZWFuX3BhaXJ3aXNlKHgsIHlbbmVhcmVzdF9mdHNdKQ0KDQojIEFkZGluZyBkYXRhIG9uIFBoaWxhZGVscGhpYSBDb21tZXJjaWFsIENvcnJpZG9ycyANCg0KUGhpbGx5Q29tQ29yciA8LQ0KICBzdF9yZWFkKCIuL2RhdGEvQ29tbWVyY2lhbF9Db3JyaWRvcnMuZ2VvanNvbiIpICU+JQ0KICBzdF90cmFuc2Zvcm0oc3RfY3JzKHRyYWN0czIxKSkNCg0KIyBJcyBpdCB3aXRoaW4gdGhlIGNvbW1lcmNpYWwgY29ycmlkb3I/DQoNCmRhdGEkd2l0aGluX2NvbV9jb3JyIDwtIGlmZWxzZShzdF93aXRoaW4oZGF0YSwgUGhpbGx5Q29tQ29yciksIDEsIDApDQoNCmRhdGEgPC0gZGF0YSAlPiUNCiAgbXV0YXRlKHdpdGhpbl9jb21fY29yciA9IGlmZWxzZShpcy5uYSh3aXRoaW5fY29tX2NvcnIpLCAwLCAxKSkNCg0KIyBNYXBwaW5nIG5lYXJlc3QgbGFuZG1hcmtzIA0KDQpuZWFyZXN0X2Z0cyA8LSBzZjo6c3RfbmVhcmVzdF9mZWF0dXJlKGRhdGEsIFBoaWxseUNvbUNvcnIpDQoNCiMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzDQoNCnggPC0gcnNnZW86OmFzX3JzZ2VvKGRhdGEpDQp5IDwtIHJzZ2VvOjphc19yc2dlbyhQaGlsbHlDb21Db3JyKQ0KDQojIENhbGN1bGF0aW5nIGRpc3RhbmNlDQoNCmRhdGEkZGlzdF90b19jb21tX2NvcnIgPC0gcnNnZW86OmRpc3RhbmNlX2V1Y2xpZGVhbl9wYWlyd2lzZSh4LCB5W25lYXJlc3RfZnRzXSkNCg0KIyBBZGRpbmcgZGF0YSBvbiBQaGlsYWRlbHBoaWEncyBMaXR0ZXIgSW5kZXgNCg0KUGhpbGx5TGl0dGVyIDwtDQogIHN0X3JlYWQoIi4vZGF0YS9MaXR0ZXJfSW5kZXguZ2VvanNvbiIpICU+JQ0KICBzdF90cmFuc2Zvcm0oc3RfY3JzKHRyYWN0czIxKSkNCg0KIyBKb2luaW5nIHRoZSBsaXR0ZXIgc2NvcmUNCg0KZGF0YSA8LSANCiBzdF9qb2luKGRhdGEsKFBoaWxseUxpdHRlciAlPiUNCiAgICAgICAgICBzZWxlY3QoLU9CSkVDVElELCAtU2hhcGVfX0FyZWEsIC1TaGFwZV9fTGVuZ3RoICklPiUNCiAgICAgICAgICByZW5hbWUobGl0dGVyID0gU0NPUkUpKSkgDQoNCiMgQWRkaW5nIGRhdGEgb24gUGhpbGFkZWxwaGlhJ3MgRmxvb2QgUGxhaW4NCg0KUGhpbGx5Rmxvb2QgPC0gDQogIHN0X3JlYWQoIi4vZGF0YS9GRU1BXzEwMF9mbG9vZF9QbGFpbi5nZW9qc29uIikgJT4lDQogIHN0X3RyYW5zZm9ybShzdF9jcnModHJhY3RzMjEpKQ0KDQojIElzIGl0IHdpdGhpbiB0aGUgZmxvb2RwbGFpbj8NCg0KZGF0YSR3aXRoaW5fZmxvb2QgPC0gaWZlbHNlKHN0X3dpdGhpbihkYXRhLCBQaGlsbHlGbG9vZCksIDEsIDApDQoNCmRhdGEgPC0gZGF0YSAlPiUNCiAgbXV0YXRlKHdpdGhpbl9mbG9vZCA9IGlmZWxzZShpcy5uYSh3aXRoaW5fZmxvb2QpLCAwLCAxKSkNCg0KIyBNYXBwaW5nIG5lYXJlc3QgZmxvb2RwbGFpbiANCg0KbmVhcmVzdF9mdHMgPC0gc2Y6OnN0X25lYXJlc3RfZmVhdHVyZShkYXRhLCBQaGlsbHlGbG9vZCkNCg0KIyBDb252ZXJ0aW5nIHRvIHJzZ2VvIGdlb21ldHJpZXMNCg0KeCA8LSByc2dlbzo6YXNfcnNnZW8oZGF0YSkNCnkgPC0gcnNnZW86OmFzX3JzZ2VvKFBoaWxseUZsb29kKQ0KDQojIENhbGN1bGF0aW5nIGRpc3RhbmNlDQoNCmRhdGEkZGlzdF90b19mbG9vZHBsYWluIDwtIHJzZ2VvOjpkaXN0YW5jZV9ldWNsaWRlYW5fcGFpcndpc2UoeCwgeVtuZWFyZXN0X2Z0c10pDQoNCiAgDQpgYGANCg0KIyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzDQoNCiMjIE1hcHBpbmcgSW50ZXJuYWwgVmFyaWFibGVzDQoNCmBgYHtyfQ0KDQojI01hcHBpbmcgSW50ZXJuYWwgVmFyaWFibGVzDQojIE1hcHBpbmcgc2FsZSBwcmljZQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHRyYWN0czIxLCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICBnZW9tX3NmKGRhdGEgPSBkYXRhLCBhZXMoY29sb3VyID0gcTUoc2FsZV9wcmljZSkpLCANCiAgICAgICAgICBzaG93LmxlZ2VuZCA9ICJwb2ludCIsIHNpemUgPSAuNzUpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZSwNCiAgICAgICAgICAgICAgICAgICBsYWJlbHM9cUJyKGRhdGEsICJzYWxlX3ByaWNlIiksDQogICAgICAgICAgICAgICAgICAgbmFtZT0iUXVpbnRpbGUgQnJlYWtzOlxuIFNhbGUgUHJpY2UiLCANCiAgICAgICAgICAgICAgICAgICBuYS52YWx1ZSA9IE5BKSArDQogIGxhYnModGl0bGU9IlByb3BlcnRpZXMgYnkgU2FsZSBQcmljZSIsIHN1YnRpdGxlID0gIlBoaWxhZGVscGhpYSAyMDIyLTIwMjMiLCANCiAgICAgIGNhcHRpb249IkZpZ3VyZSAzLjEuMSIpICsNCiAgbWFwVGhlbWUoKQ0KDQojIE1hcHBpbmcgU2l6ZQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHRyYWN0czIxLCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICBnZW9tX3NmKGRhdGEgPSBkYXRhLCBhZXMoY29sb3VyID0gcTUodG90YWxfbGl2YWJsZV9hcmVhKSksIA0KICAgICAgICAgIHNob3cubGVnZW5kID0gInBvaW50Iiwgc2l6ZSA9IC43NSkgKw0KICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlLA0KICAgICAgICAgICAgICAgICAgIGxhYmVscz1xQnIoZGF0YSwgInRvdGFsX2xpdmFibGVfYXJlYSIpLA0KICAgICAgICAgICAgICAgICAgIG5hbWU9IlF1aW50aWxlIEJyZWFrczpcbkFyZWEgaW4gU3EgRnQiLCANCiAgICAgICAgICAgICAgICAgICBuYS52YWx1ZSA9IE5BKSArDQogIGxhYnModGl0bGU9IlByb3BlcnRpZXMgYnkgU2l6ZSIsIHN1YnRpdGxlID0gIlBoaWxhZGVscGhpYSAyMDIyLTIwMjMiLCANCiAgICAgIGNhcHRpb249IkZpZ3VyZSAzLjEuMiIpICsNCiAgbWFwVGhlbWUoKQ0KDQojIE1hcHBpbmcgSW50ZXJpb3IgQ29uZGl0aW9uDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gdHJhY3RzMjEsIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZGFya2dyZXkiKSArDQogIGdlb21fc2YoZGF0YSA9IGRhdGEsIGFlcyhjb2xvdXIgPSAoSW50ZXJpb3JDb25kaXRpb25UeXBlKSksIA0KICAgICAgICAgIHNob3cubGVnZW5kID0gInBvaW50Iiwgc2l6ZSA9IC41KSArDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWUsIG5hbWU9IkludGVyaW9yIENvbmRpdGlvbiIsDQogICAgICAgICAgICAgICAgICAgbmEudmFsdWUgPSBOQSkgKw0KICBsYWJzKHRpdGxlPSJQcm9wZXJ0aWVzIGJ5IEludGVybmFsIENvbmRpdGlvbiIsIHN1YnRpdGxlID0gIlBoaWxhZGVscGhpYSAyMDIyLTIwMjMiLCANCiAgICAgIGNhcHRpb249IkZpZ3VyZSAzLjEuMyIpICsNCiAgbWFwVGhlbWUoKQ0KDQpgYGANCg0KDQojIyBNYXBwaW5nIEV4dGVybmFsIFZhcmlhYmxlcw0KDQpgYGB7cn0NCiMjIE1hcHBpbmcgRXh0ZXJuYWwgVmFyaWFibGVzDQoNCiMgTWFwcGluZyBwcm9wZXJ0aWVzIGFyb3VuZCBzY2hvb2xzDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gdHJhY3RzMjEsIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZGFya2dyZXkiKSArDQogIGdlb21fc2YoZGF0YSA9IGRhdGEsIGFlcyhjb2xvdXIgPSBxNShzYWxlX3ByaWNlKSksIA0KICAgICAgICAgIHNob3cubGVnZW5kID0gInBvaW50Iiwgc2l6ZSA9IC43NSwgYWxwaGE9MC4zKSArIA0KICBnZW9tX3NmKGRhdGEgPSBQaGlsbHlTY2hvb2xzLCBjb2xvdXIgPSAiYmxhY2siLCBzaXplID0gMS41LCBhbHBoYT0wLjYpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZSwNCiAgICAgICAgICAgICAgICAgICBsYWJlbHM9cUJyKGRhdGEsICJzYWxlX3ByaWNlIiksDQogICAgICAgICAgICAgICAgICAgbmFtZT0iUXVpbnRpbGUgQnJlYWtzOlxuU2FsZSBQcmljZXMiLCANCiAgICAgICAgICAgICAgICAgICBuYS52YWx1ZSA9IE5BKSArIA0KICBsYWJzKHRpdGxlPSJQcm9wZXJ0aWVzIEFyb3VuZCBTY2hvb2xzIiwgc3VidGl0bGUgPSAiUGhpbGFkZWxwaGlhIDIwMjItMjAyMyIsIA0KICAgICAgY2FwdGlvbj0iRmlndXJlIDMuMi4xIikgKw0KICBtYXBUaGVtZSgpDQoNCiMgTWFwcGluZyBwcm9wZXJ0aWVzIGFyb3VuZCBjb21tZXJjaWFsIGNvcnJpZG9ycw0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHRyYWN0czIxLCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICBnZW9tX3NmKGRhdGEgPSBkYXRhLCBhZXMoY29sb3VyID0gcTUoc2FsZV9wcmljZSkpLCANCiAgICAgICAgICBzaG93LmxlZ2VuZCA9ICJwb2ludCIsIHNpemUgPSAuNzUsIGFscGhhPTAuMykgKyANCiAgZ2VvbV9zZihkYXRhID0gUGhpbGx5Q29tQ29yciwgY29sb3VyID0gImJsYWNrIiwgZmlsbD0iYmxhY2siLCBhbHBoYT0wLjYpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZSwNCiAgICAgICAgICAgICAgICAgICBsYWJlbHM9cUJyKGRhdGEsICJzYWxlX3ByaWNlIiksDQogICAgICAgICAgICAgICAgICAgbmFtZT0iUXVpbnRpbGUgQnJlYWtzOlxuU2FsZSBQcmljZXMiLCANCiAgICAgICAgICAgICAgICAgICBuYS52YWx1ZSA9IE5BKSArIA0KICBsYWJzKHRpdGxlPSJQcm9wZXJ0aWVzIEFyb3VuZCBDb21tZXJjaWFsIENvcnJpZG9ycyIsIHN1YnRpdGxlID0gIlBoaWxhZGVscGhpYSAyMDIyLTIwMjMiLCANCiAgICAgIGNhcHRpb249IkZpZ3VyZSAzLjIuMiIpICsNCiAgbWFwVGhlbWUoKQ0KDQojIE1hcHBpbmcgcHJvcGVydGllcyBhcm91bmQgbGFuZG1hcmtzDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gdHJhY3RzMjEsIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZGFya2dyZXkiKSArDQogIGdlb21fc2YoZGF0YSA9IGRhdGEsIGFlcyhjb2xvdXIgPSBxNShzYWxlX3ByaWNlKSksIA0KICAgICAgICAgIHNob3cubGVnZW5kID0gInBvaW50Iiwgc2l6ZSA9IC43NSwgYWxwaGE9MC4zKSArIA0KICBnZW9tX3NmKGRhdGEgPSBQaGlsbHlMYW5kbWFya3MsIGNvbG91ciA9ICJibGFjayIsIGZpbGw9ImJsYWNrIiwgc2l6ZSA9IC43NSwgYWxwaGE9MC42KSArDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWUsDQogICAgICAgICAgICAgICAgICAgbGFiZWxzPXFCcihkYXRhLCAic2FsZV9wcmljZSIpLA0KICAgICAgICAgICAgICAgICAgIG5hbWU9IlF1aW50aWxlIEJyZWFrczpcblNhbGUgUHJpY2VzIiwgDQogICAgICAgICAgICAgICAgICAgbmEudmFsdWUgPSBOQSkgKyANCiAgbGFicyh0aXRsZT0iUHJvcGVydGllcyBBcm91bmQgTGFuZG1hcmtzIiwgc3VidGl0bGUgPSAiUGhpbGFkZWxwaGlhIDIwMjItMjAyMyIsIA0KICAgICAgY2FwdGlvbj0iRmlndXJlIDMuMi4zIikgKw0KICBtYXBUaGVtZSgpDQoNCiMgTWFwcGluZyBwcm9wZXJ0aWVzIGFyb3VuZCBmbG9vZHBsYWlucw0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHRyYWN0czIxLCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICBnZW9tX3NmKGRhdGEgPSBkYXRhLCBhZXMoY29sb3VyID0gcTUoc2FsZV9wcmljZSkpLCANCiAgICAgICAgICBzaG93LmxlZ2VuZCA9ICJwb2ludCIsIHNpemUgPSAuNzUsIGFscGhhPTAuMykgKyANCiAgZ2VvbV9zZihkYXRhID0gUGhpbGx5Rmxvb2QsIGNvbG91ciA9ICJibGFjayIsIGZpbGw9ImJsYWNrIiwgc2l6ZSA9IC43NSwgYWxwaGE9MC42KSArDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWUsDQogICAgICAgICAgICAgICAgICAgbGFiZWxzPXFCcihkYXRhLCAic2FsZV9wcmljZSIpLA0KICAgICAgICAgICAgICAgICAgIG5hbWU9IlF1aW50aWxlIEJyZWFrczpcblNhbGUgUHJpY2VzIiwgDQogICAgICAgICAgICAgICAgICAgbmEudmFsdWUgPSBOQSkgKyANCiAgbGFicyh0aXRsZT0iUHJvcGVydGllcyBBcm91bmQgRmxvb2QgUGxhaW4iLCBzdWJ0aXRsZSA9ICJQaGlsYWRlbHBoaWEgMjAyMi0yMDIzIiwgDQogICAgICBjYXB0aW9uPSJGaWd1cmUgMy4yLjQiKSArDQogIG1hcFRoZW1lKCkNCg0KI2xpYnJhcnkoZ3JpZEV4dHJhKQ0KDQojZ3JpZC5hcnJhbmdlKA0KICAjYjEsDQogICNiMiwNCiAgI2IzLA0KICAjYjQsDQogICNucm93ID0gMiwNCiAgI3dpZHRocz1jKDQsNCksDQogICN0b3AgPSAiVGl0bGUgb2YgdGhlIHBhZ2UiDQogICMpDQoNCg0KYGBgDQoNCg0KIyMgTWFwcGluZyBEZW1vZ3JhcGhpYyBWYXJpYWJsZXMNCg0KYGBge3IgUHJpY2UvU3FmdCwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQoNCnRyYWN0czIxIDwtIA0KICB0cmFjdHMyMSAlPiUNCiAgbXV0YXRlKFBjdFdoaXRlID0gKChyYWNlX3doaXRlL3RvdGFscG9wKSoxMDApLA0KICAgICAgICAgUGN0QmxhY2sgPSAoKHJhY2VfYmxhY2svdG90YWxwb3ApKjEwMCksDQogICAgICAgICBQY3RIaXNwYW5pYyA9ICgoaGlzcGFuaWNfbGF0aW5vL3RvdGFscG9wKSoxMDApLA0KICAgICAgICAgUGN0QmFjaGVsb3JzID0gKChlZHVfYmFjaGVsb3JzL3RvdGFscG9wKSoxMDApLA0KICAgICAgICAgUGN0UG92ZXJ0eSA9ICgocG9wX2JlbG93X3BvdmVydHkvdG90YWxwb3ApKjEwMCkpDQoNCmBgYA0KDQpgYGB7cn0NCiMjIE1hcHBpbmcgRGVtb2dyYXBoaWMgVmFyaWFibGVzDQoNCiMgTWFwcGluZyBtZWRpYW4gaW5jb21lIGFyb3VuZCBzb2xkIHByb3BlcnRpZXMNCg0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHRyYWN0czIxLCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICBnZW9tX3NmKGRhdGEgPSB0cmFjdHMyMSwgYWVzKGZpbGwgPSBxNShtZWRfaW5jb21lKSksIGNvbG9yID0gInRyYW5zcGFyZW50IiwgYWxwaGE9MC41KSArDQogICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCdVB1IiwNCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBxQnIodHJhY3RzMjEsICJtZWRfaW5jb21lIiksDQogICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJNZWRpYW4gSW5jb21lXG5RdWludGlsZSBCcmVha3MiKSArDQogIGdlb21fc2YoZGF0YSA9IGRhdGEsIGFlcyhjb2xvdXIgPSBxNShzYWxlX3ByaWNlKSksIA0KICAgICAgICAgIHNob3cubGVnZW5kID0gInBvaW50Iiwgc2l6ZSA9IC4yKSArDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWUsDQogICAgICAgICAgICAgICAgICAgbGFiZWxzPXFCcihkYXRhLCAic2FsZV9wcmljZSIpLA0KICAgICAgICAgICAgICAgICAgIG5hbWU9IlF1aW50aWxlIEJyZWFrczpcblNhbGUgUHJpY2VzIiwgDQogICAgICAgICAgICAgICAgICAgbmEudmFsdWUgPSBOQSkgKyANCiAgbGFicyh0aXRsZT0iTWVkaWFuIEluY29tZSBBcm91bmQgU29sZCBQcm9wZXJ0aWVzIiwgc3VidGl0bGUgPSAiUGhpbGFkZWxwaGlhIDIwMjItMjAyMyIsIA0KICAgICAgY2FwdGlvbj0iRmlndXJlIDMuMy4xIikNCg0KDQoNCiMgTWFwcGluZyB3aGl0ZSBwb3B1bGF0aW9uIGFyb3VuZCBzb2xkIHByb3BlcnRpZXMNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSB0cmFjdHMyMSwgZmlsbCA9ICJncmV5ODkiLCBjb2xvciA9ICJkYXJrZ3JleSIpICsNCiAgZ2VvbV9zZihkYXRhID0gdHJhY3RzMjEsIGFlcyhmaWxsID0gcTUoUGN0V2hpdGUpKSwgY29sb3IgPSAidHJhbnNwYXJlbnQiLCBhbHBoYT0wLjUpICsNCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJ1UHUiLA0KICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHFCcih0cmFjdHMyMSwgIlBjdFdoaXRlIiksDQogICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICIlIFdoaXRlIFBvcHVsYXRpb25cblF1aW50aWxlIEJyZWFrcyIpICsNCiAgZ2VvbV9zZihkYXRhID0gZGF0YSwgYWVzKGNvbG91ciA9IHE1KHNhbGVfcHJpY2UpKSwgDQogICAgICAgICAgc2hvdy5sZWdlbmQgPSAicG9pbnQiLCBzaXplID0gLjIpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZSwNCiAgICAgICAgICAgICAgICAgICBsYWJlbHM9cUJyKGRhdGEsICJzYWxlX3ByaWNlIiksDQogICAgICAgICAgICAgICAgICAgbmFtZT0iUXVpbnRpbGUgQnJlYWtzOlxuU2FsZSBQcmljZXMiLCANCiAgICAgICAgICAgICAgICAgICBuYS52YWx1ZSA9IE5BKSArIA0KICBsYWJzKHRpdGxlPSIlIFdoaXRlIFBvcHVsYXRpb24gQXJvdW5kIFNvbGQgUHJvcGVydGllcyIsIHN1YnRpdGxlID0gIlBoaWxhZGVscGhpYSAyMDIyLTIwMjMiLCANCiAgICAgIGNhcHRpb249IkZpZ3VyZSAzLjMuMiIpDQoNCiMgTWFwcGluZyBibGFjayBwb3B1bGF0aW9uIGFyb3VuZCBzb2xkIHByb3BlcnRpZXMNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSB0cmFjdHMyMSwgZmlsbCA9ICJncmV5ODkiLCBjb2xvciA9ICJkYXJrZ3JleSIpICsNCiAgZ2VvbV9zZihkYXRhID0gdHJhY3RzMjEsIGFlcyhmaWxsID0gcTUoUGN0QmxhY2spKSwgY29sb3IgPSAidHJhbnNwYXJlbnQiLCBhbHBoYT0wLjUpICsNCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJ1UHUiLA0KICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHFCcih0cmFjdHMyMSwgIlBjdEJsYWNrIiksDQogICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICIlIEJsYWNrIFBvcHVsYXRpb25cblF1aW50aWxlIEJyZWFrcyIpICsNCiAgZ2VvbV9zZihkYXRhID0gZGF0YSwgYWVzKGNvbG91ciA9IHE1KHNhbGVfcHJpY2UpKSwgDQogICAgICAgICAgc2hvdy5sZWdlbmQgPSAicG9pbnQiLCBzaXplID0gLjIpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZSwNCiAgICAgICAgICAgICAgICAgICBsYWJlbHM9cUJyKGRhdGEsICJzYWxlX3ByaWNlIiksDQogICAgICAgICAgICAgICAgICAgbmFtZT0iUXVpbnRpbGUgQnJlYWtzOlxuU2FsZSBQcmljZXMiLCANCiAgICAgICAgICAgICAgICAgICBuYS52YWx1ZSA9IE5BKSArIA0KICBsYWJzKHRpdGxlPSIlIEJsYWNrIFBvcHVsYXRpb24gQXJvdW5kIFNvbGQgUHJvcGVydGllcyIsIHN1YnRpdGxlID0gIlBoaWxhZGVscGhpYSAyMDIyLTIwMjMiLCANCiAgICAgIGNhcHRpb249IkZpZ3VyZSAzLjMuMyIpDQoNCiMgTWFwcGluZyBwb3B1bGF0aW9uIHdpdGggYmFjaGVsb3IncyBkZWdyZWUgYXJvdW5kIHNvbGQgcHJvcGVydGllcw0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHRyYWN0czIxLCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICBnZW9tX3NmKGRhdGEgPSB0cmFjdHMyMSwgYWVzKGZpbGwgPSBxNShQY3RCYWNoZWxvcnMpKSwgY29sb3IgPSAidHJhbnNwYXJlbnQiLCBhbHBoYT0wLjUpICsNCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJ1UHUiLA0KICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHFCcih0cmFjdHMyMSwgIlBjdEJhY2hlbG9ycyIpLA0KICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiJSBCYWNoZWxvcidzIERlZ3JlZVxuUXVpbnRpbGUgQnJlYWtzIikgKw0KICBnZW9tX3NmKGRhdGEgPSBkYXRhLCBhZXMoY29sb3VyID0gcTUoc2FsZV9wcmljZSkpLCANCiAgICAgICAgICBzaG93LmxlZ2VuZCA9ICJwb2ludCIsIHNpemUgPSAuMikgKw0KICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlLA0KICAgICAgICAgICAgICAgICAgIGxhYmVscz1xQnIoZGF0YSwgInNhbGVfcHJpY2UiKSwNCiAgICAgICAgICAgICAgICAgICBuYW1lPSJRdWludGlsZSBCcmVha3M6XG5TYWxlIFByaWNlcyIsIA0KICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlID0gTkEpICsgDQogIGxhYnModGl0bGU9IiUgUG9wdWxhdGlvbiB3aXRoIEJhY2hlbG9yJ3MgRGVncmVlIEFyb3VuZCBTb2xkIFByb3BlcnRpZXMiLCBzdWJ0aXRsZSA9ICJQaGlsYWRlbHBoaWEgMjAyMi0yMDIzIiwgDQogICAgICBjYXB0aW9uPSJGaWd1cmUgMy4zLjQiKQ0KDQojIE1hcHBpbmcgcG9wdWxhdGlvbiBpbiBwb3ZlcnR5IGFyb3VuZCBzb2xkIHByb3BlcnRpZXMNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSB0cmFjdHMyMSwgZmlsbCA9ICJncmV5ODkiLCBjb2xvciA9ICJkYXJrZ3JleSIpICsNCiAgZ2VvbV9zZihkYXRhID0gdHJhY3RzMjEsIGFlcyhmaWxsID0gcTUoUGN0UG92ZXJ0eSkpLCBjb2xvciA9ICJ0cmFuc3BhcmVudCIsIGFscGhhPTAuNSkgKw0KICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiQnVQdSIsDQogICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gcUJyKHRyYWN0czIxLCAiUGN0UG92ZXJ0eSIpLA0KICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiJSBQb3AgaW4gUG92ZXJ0eVxuUXVpbnRpbGUgQnJlYWtzIikgKw0KICBnZW9tX3NmKGRhdGEgPSBkYXRhLCBhZXMoY29sb3VyID0gcTUoc2FsZV9wcmljZSkpLCANCiAgICAgICAgICBzaG93LmxlZ2VuZCA9ICJwb2ludCIsIHNpemUgPSAuMikgKw0KICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlLA0KICAgICAgICAgICAgICAgICAgIGxhYmVscz1xQnIoZGF0YSwgInNhbGVfcHJpY2UiKSwNCiAgICAgICAgICAgICAgICAgICBuYW1lPSJRdWludGlsZSBCcmVha3M6XG5TYWxlIFByaWNlcyIsIA0KICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlID0gTkEpICsgDQogIGxhYnModGl0bGU9IiUgUG9wdWxhdGlvbiBpbiBQb3ZlcnR5IEFyb3VuZCBTb2xkIFByb3BlcnRpZXMiLCBzdWJ0aXRsZSA9ICJQaGlsYWRlbHBoaWEgMjAyMi0yMDIzIiwgDQogICAgICBjYXB0aW9uPSJGaWd1cmUgMy4zLjUiKSsNCiAgbWFwVGhlbWUoKQ0KDQpgYGANCmBgYHtyfQ0KDQojIGpvaW5pbmcgY2Vuc3VzIGRhdGENCg0KZGF0YSA8LSANCiAgc3Rfam9pbihkYXRhLCB0cmFjdHMyMSkNCg0KYGBgDQoNCmBgYHtyfQ0KSW50ZXJuYWxWYXJpYWJsZXMgPC0gZGF0YSANCg0KSW50ZXJuYWxWYXJpYWJsZXMgPC0gc3RfZHJvcF9nZW9tZXRyeShJbnRlcm5hbFZhcmlhYmxlcykNCg0KSW50ZXJuYWxWYXJpYWJsZXMgPC0gSW50ZXJuYWxWYXJpYWJsZXMgJT4lDQogIGRwbHlyOjpzZWxlY3QoInNhbGVfcHJpY2UiLCAiUHJpY2VQZXJTcWZ0IiwgInRvdGFsX2xpdmFibGVfYXJlYSIsICJ5ZWFyX2J1aWx0IiwgIm51bWJlcl9vZl9yb29tcyIsICJudW1iZXJfb2ZfYmF0aHJvb21zIiwgIm51bWJlcl9vZl9iZWRyb29tcyIpIA0KDQpzdGFyZ2F6ZXIoYXMuZGF0YS5mcmFtZShJbnRlcm5hbFZhcmlhYmxlcyksIHR5cGU9InRleHQiLCBkaWdpdHM9MSwgdGl0bGUgPSAiRGVzY3JpcHRpdmUgU3RhdGlzdGljcyBmb3IgUGhpbGFkZWxwaGlhIEhvbWVzIEludGVybmFsIFZhcmlhYmxlcyAoRmlndXJlIDQuMSkiLCBvdXQgPSAiVHJhaW5pbmdfUEhMSW50ZXJuYWwudHh0IikNCmBgYA0KDQpgYGB7cn0NCkRlbW9ncmFwaGljVmFyaWFibGVzIDwtIGRhdGEgDQoNCkRlbW9ncmFwaGljVmFyaWFibGVzIDwtIHN0X2Ryb3BfZ2VvbWV0cnkoRGVtb2dyYXBoaWNWYXJpYWJsZXMpDQoNCkRlbW9ncmFwaGljVmFyaWFibGVzIDwtIERlbW9ncmFwaGljVmFyaWFibGVzICU+JQ0KICBkcGx5cjo6c2VsZWN0KCJQY3RXaGl0ZSIsICJQY3RCbGFjayIsICJQY3RIaXNwYW5pYyIsICJQY3RCYWNoZWxvcnMiLCAiUGN0UG92ZXJ0eSIsICJtZWRfaW5jb21lIikgDQoNCnN0YXJnYXplcihhcy5kYXRhLmZyYW1lKERlbW9ncmFwaGljVmFyaWFibGVzKSwgdHlwZT0idGV4dCIsIGRpZ2l0cz0xLCB0aXRsZSA9ICJEZXNjcmlwdGl2ZSBTdGF0aXN0aWNzIGZvciBQaGlsYWRlbHBoaWEgSG9tZXMgU3BhdGlhbCBWYXJpYWJsZXMgKEZpZ3VyZSA0LjEpIiwgb3V0ID0gIlRyYWluaW5nX1BITFNwYXRpYWwudHh0IikNCmBgYA0KYGBge3IgZmlnLmFsaWduPSJjZW50ZXIiLCB3YXJuaW5nPUZBTFNFLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDE0fQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTk5KSAjdHVybnMgb2ZmIHNjaWVudGlmaWMgbm90YXRpb24gYW5kIGhvdyBtYW55IHBvaW50cw0KDQpWYXJpYWJsZSA8LSBkYXRhICU+JQ0KICBmaWx0ZXIoc2FsZV9wcmljZSA8IDEwMDAwMDAwKQ0KDQojIyBTY2F0dGVycGxvdA0Kc3RfZHJvcF9nZW9tZXRyeShWYXJpYWJsZSkgJT4lIA0KICBkcGx5cjo6c2VsZWN0KHNhbGVfcHJpY2UsIFByaWNlUGVyU3FmdCwgeWVhcl9idWlsdCwgbnVtYmVyX29mX2JhdGhyb29tcywgbnVtYmVyX29mX2JlZHJvb21zLCBudW1iZXJfb2Zfcm9vbXMsIHRvdGFsX2xpdmFibGVfYXJlYSwgbWVkX2luY29tZSwgUGN0V2hpdGUsUGN0QmxhY2ssIFBjdEhpc3BhbmljLCBQY3RCYWNoZWxvcnMsIFBjdFBvdmVydHksICkgJT4lIA0KICBmaWx0ZXIoc2FsZV9wcmljZSA8IDEwMDAwMDAwKSAlPiUNCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLXNhbGVfcHJpY2UpICU+JSANCiAgIGdncGxvdChhZXMoVmFsdWUsIHNhbGVfcHJpY2UpKSArDQogICAgIGdlb21fcG9pbnQoc2l6ZSA9IC41KSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlPUYsIGNvbG91ciA9ICIjRkE3ODAwIikgKw0KICAgICBmYWNldF93cmFwKH5WYXJpYWJsZSwgbmNvbCA9IDMsIHNjYWxlcyA9ICJmcmVlIikgKw0KICAgICBsYWJzKHRpdGxlID0gIlByaWNlIGFzIGEgZnVuY3Rpb24gb2YgY29udGludW91cyB2YXJpYWJsZXMiLCANCiAgICAgICAgICBjYXB0aW9uPSJGaWd1cmUgNC4xIikgKw0KICAgICBwbG90VGhlbWUoKQ0KYGBgDQoNCiMjIHRpdGxlDQoNCg0KDQoNCg0KDQoNCg0KDQoNCmBgYHtyfQ0KI25vdCBuZWVkZWQgDQoNCiNwbG90dGluZyB0aGUgY29ycmVsYXRpb25zIA0KDQpzdF9kcm9wX2dlb21ldHJ5KGRhdGEpICU+JSANCiAgZHBseXI6OnNlbGVjdChzYWxlX3ByaWNlLCBtZWRfaW5jb21lLCBkaXN0X3RvX3B2dF9zY2hvb2xzKSAlPiUNCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLXNhbGVfcHJpY2UpICU+JSANCiAgIGdncGxvdChhZXMoVmFsdWUsIHNhbGVfcHJpY2UpKSArDQogICAgIGdlb21fcG9pbnQoc2l6ZSA9IC41KSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlPUYsIGNvbG91ciA9ICIjRkE3ODAwIikgKw0KICAgICBmYWNldF93cmFwKH5WYXJpYWJsZSwgbmNvbCA9IDMsIHNjYWxlcyA9ICJmcmVlIikgKw0KICAgICBsYWJzKHRpdGxlID0gIlByaWNlIGFzIGEgZnVuY3Rpb24gb2YgY29udGludW91cyB2YXJpYWJsZXMiKSArDQogICAgIHBsb3RUaGVtZSgpDQpgYGANCg0KYGBge3J9DQoNCnZhcmlhYmxlcyA8LSBjKA0KICAic2FsZV9wcmljZSIsICJkaXN0X3RvX3B2dF9zY2hvb2xzIiwgIm51bWJlcl9vZl9iYXRocm9vbXMiLA0KICAibnVtYmVyX29mX2JlZHJvb21zIiwgIm1lZF9pbmNvbWUiLCAid2l0aGluX2Zsb29kIiwNCiAgIndpdGhpbl9jb21fY29yciIsICJyYWNlX3doaXRlIiwgInJhY2VfYmxhY2siLCAidG90YWxfbGl2YWJsZV9hcmVhIiwgICJQcmljZVBlclNxZnQiLCAieWVhcl9idWlsdCIsICJudW1iZXJfb2Zfcm9vbXMiLCAidG90YWxfbGl2YWJsZV9hcmVhIiwgIlBjdEJhY2hlbG9ycyIsICJQY3RQb3ZlcnR5Ig0KKQ0KDQpudW1lcmljVmFycyA8LSBkYXRhICU+JQ0KICBzdF9kcm9wX2dlb21ldHJ5KGRhdGEpICU+JQ0KICBzZWxlY3QodmFyaWFibGVzKSU+JQ0KICBuYS5vbWl0KCkNCg0KZ2djb3JycGxvdCgNCiAgcm91bmQoY29yKG51bWVyaWNWYXJzKSwgMSksIA0KICBwLm1hdCA9IGNvcl9wbWF0KG51bWVyaWNWYXJzKSwNCiAgY29sb3JzID0gYygnI2Q3MTkxYycsJyNmZmZmYmYnLCcjMmM3YmI2JyksDQogIHR5cGU9Imxvd2VyIiwNCiAgaW5zaWcgPSAiYmxhbmsiKSArICANCiAgICBsYWJzKHRpdGxlID0gIkNvcnJlbGF0aW9uIGFjcm9zcyBudW1lcmljIHZhcmlhYmxlcyIpIA0KIyAgcGxvdFRoZW1lKCkNCmBgYA0KDQojIFJlZ3Jlc3Npb25zDQoNCmBgYHtyfQ0KDQojbm90IHN1cmUgaWYgYWxsIHRoaXMgaXMgbmVlZGVkDQoNCiNoZXJlIHdlIGRvIGl0IHdpdGggdGhlIGNvbXBsZXRlIGRhdGFzZXQgZm9yIHNvbWUgcmVhc29uPyANCg0KcmVnMSA8LSBsbShzYWxlX3ByaWNlIH4gLiwgZGF0YSA9IHN0X2Ryb3BfZ2VvbWV0cnkoZGF0YSkgJT4lICANCiAgICAgICAgICAgICAjZHBseXI6OmZpbHRlcih0b1ByZWRpY3QgPT0gIk1PREVMTElORyIpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdCh2YXJpYWJsZXMpKQ0KDQpzdW1tYXJ5KHJlZzEpDQoNCiMgcHJldHR5IHJlZ3Jlc3Npb24NCg0KcHJpbnQoc3RhcmdhemVyKHJlZzEsIHR5cGU9InRleHQiLCBkaWdpdHM9MSwgdGl0bGU9IkxpbmVhciBNb2RlbCBvZiBUcmFpbmluZyBEYXRhc2V0IChGaWd1cmUgNSkiLCBvdXQgPSAiVHJhaW5pbmcgTE0udHh0IikpDQoNCmBgYA0KDQpgYGB7cn0NCg0KDQojIyBDcmVhdGluZyB0aGUgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0DQptb2RlbGxpbmdfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIodG9QcmVkaWN0ID09ICJNT0RFTExJTkciKQ0KDQpzZXQuc2VlZCg5OTkpICNtYWtlcyBzdXJlIGRhdGEgaXMgc3BsaXQgc2FtZSB3YXkgZXZlcnkgdGltZQ0KDQojIyBTcGxpdHRpbmcgdGhlIGRhdGENCnNwbGl0IDwtIHNhbXBsZS5zcGxpdChtb2RlbGxpbmdfZGF0YSRvYmplY3RpZCwgU3BsaXRSYXRpbyA9IDAuNzUpDQoNCiMjIENyZWF0aW5nIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBzZXRzDQp0cmFpbl9kYXRhIDwtIG1vZGVsbGluZ19kYXRhW3NwbGl0LF0NCnRlc3RfZGF0YSA8LSBtb2RlbGxpbmdfZGF0YVshc3BsaXQsXQ0KYGBgDQoNCmBgYHtyfQ0KDQojcmVncmVzc2lvbiBvbiB0cmFpbmluZyBkYXRhDQoNCnJlZzIgPC0gbG0oc2FsZV9wcmljZSB+IC4sIGRhdGEgPSBzdF9kcm9wX2dlb21ldHJ5KHRyYWluX2RhdGEpICU+JSAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHZhcmlhYmxlcykpDQoNCiMjIFBsb3QgcmVncmVzc2lvbiAoZGlkbnQgd29yaykNCiMgZWZmZWN0X3Bsb3QocmVnMiwgcHJlZCA9IHRvdGFsX2xpdmFibGVfYXJlYSwgaW50ZXJ2YWwgPSBUUlVFLCBwbG90LnBvaW50cyA9IFRSVUUpDQoNCg0KdGVzdF9kYXRhIDwtDQogIHRlc3RfZGF0YSAlPiUNCiAgbXV0YXRlKFByaWNlLlByZWRpY3QgPSBwcmVkaWN0KHJlZzIsIHRlc3RfZGF0YSksDQogICAgICAgICBQcmljZS5FcnJvciA9IFByaWNlLlByZWRpY3QgLSBzYWxlX3ByaWNlLA0KICAgICAgICAgUHJpY2UuQWJzRXJyb3IgPSBhYnMoUHJpY2UuUHJlZGljdCAtIHNhbGVfcHJpY2UpLA0KICAgICAgICAgUHJpY2UuQVBFID0gKGFicyhQcmljZS5QcmVkaWN0IC0gc2FsZV9wcmljZSkpIC8gUHJpY2UuUHJlZGljdCkNCg0KYGBgDQoNCmBgYHtyfQ0KIyB0YWJsZSBvZiBNQUUgYW5kIE1BUEUNCg0KIyMgQWNjdXJhY3kgLSBNZWFuIEFic29sdXRlIEVycm9yDQpNQUUgPC0gZGF0YS5mcmFtZShtZWFuKHRlc3RfZGF0YSRQcmljZS5BYnNFcnJvciwgbmEucm0gPSBUKSkNCk1BUEUgPC0gZGF0YS5mcmFtZShtZWFuKHRlc3RfZGF0YSRQcmljZS5BUEUsIG5hLnJtID0gVCkpICNNQVBFDQoNCnJlZy5NQUUuTUFQRSA8LSANCiAgY2JpbmQoTUFFLCBNQVBFKSAlPiUNCiAga2FibGUoY2FwdGlvbiA9ICJSZWdyZXNzaW9uIE1BRSAmIE1BUEUgKEZpZ3VyZSA1LjEpIikgJT4lDQogIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiLGZ1bGxfd2lkdGggPSBGKSANCg0KcmVnLk1BRS5NQVBFDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQp0ZXN0X2RhdGEgPC0NCiAgdGVzdF9kYXRhICU+JQ0KICBmaWx0ZXIoc2FsZV9wcmljZSA8IDEwMDAwMDAwKQ0KDQojIyBLLUZvbGQ6IEdlbmVyYWxpemFiaWxpdHkgQ3Jvc3MtVmFsaWRhdGlvbg0KDQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMDApDQpzZXQuc2VlZCgzNjkpDQoNCnJlZy5jdiA8LSANCiAgdHJhaW4oc2FsZV9wcmljZSB+IC4sIGRhdGEgPSBzdF9kcm9wX2dlb21ldHJ5KHRlc3RfZGF0YSkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHZhcmlhYmxlcyksDQptZXRob2QgPSAibG0iLCB0ckNvbnRyb2wgPSBmaXRDb250cm9sLCBuYS5hY3Rpb24gPSBuYS5wYXNzKQ0KDQojVGhpcyBuZWVkcyB0byBiZSB3b3JrLXNob3BwZWQuIEl0IHN0b3BwZWQgd29ya2luZyBhbmQgSSBkb24ndCBrbm93IHdoeS4gVGhlIGNvZGUgd2l0aG91dCBhbiBvYmplY3QgYXNzaWduZWQgYWxzbyBvdXRwdXRzLg0KDQojcmVnLmN2JHJlc2FtcGxlWzE6NSxdDQoNCiNtZWFuKHJlZy5jdiRyZXNhbXBsZVssM10pDQoNCiNyZWcuY3YkcmVzYW1wbGVbNyxdDQoNCg0Kc3RhcmdhemVyKGFzLmRhdGEuZnJhbWUocmVnLmN2JHJlc2FtcGxlKSwgdHlwZT0idGV4dCIsIGRpZ2l0cz0xLCB0aXRsZT0iQ3Jvc3MgVmFsaWRhdGlvbiBSZXN1bHRzICg1LjIpIiwgb3V0ID0gIkNWLnR4dCIpICNhbGwgY3YNCg0KYGBgDQoNCmBgYHtyfQ0KI3Bsb3R0aW5nIHRoZSBjcm9zcyB2YWxpZGF0aW9uIHN0dWZmDQoNCmdncGxvdChyZWcuY3YkcmVzYW1wbGUsIGFlcyh4PU1BRSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJibHVlIikgKw0KICBsYWJzKHRpdGxlID0gIkNyb3NzIFZhbGlkYXRpb24gVGVzdHMgaW4gTWVhbiBBdmVyYWdlIEVycm9yIiwgY2FwdGlvbj0iRmlndXJlIDUuMyIpICsNCiAgcGxvdFRoZW1lKCkNCg0KYGBgDQpgYGB7cn0NCg0KdGVzdF9kYXRhICU+JQ0KICBkcGx5cjo6c2VsZWN0KFByaWNlLlByZWRpY3QsIHNhbGVfcHJpY2UpICU+JQ0KICAgIGdncGxvdChhZXMoc2FsZV9wcmljZSwgUHJpY2UuUHJlZGljdCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgc3RhdF9zbW9vdGgoYWVzKHNhbGVfcHJpY2UsIHNhbGVfcHJpY2UpLCANCiAgICAgICAgICAgICBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBzaXplID0gMSwgY29sb3VyPSIjZDcxOTFjIikgKyANCiAgc3RhdF9zbW9vdGgoYWVzKFByaWNlLlByZWRpY3QsIHNhbGVfcHJpY2UpLCANCiAgICAgICAgICAgICAgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgc2l6ZSA9IDEsIGNvbG91cj0iIzJjN2JiNiIpICsNCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIFNhbGUgUHJpY2UgYXMgYSBGdW5jdGlvbiBvZiBPYnNlcnZlZCBQcmljZSIsDQogICAgICAgc3VidGl0bGU9IlJlZCBsaW5lIHJlcHJlc2VudHMgYSBwZXJmZWN0IHByZWRpY3Rpb247IEJsdWUgbGluZSByZXByZXNlbnRzIHByZWRpY3Rpb24iLA0KICAgICAgIGNhcHRpb24gPSAiRmlndXJlIDYuMiIpICsNCiAgcGxvdFRoZW1lKCkNCmBgYA0KDQpgYGB7cn0NCg0KI3VzZSB0aGUgc2FtZSB0aG5nIGZvciB3aWdodHMgYW5kIG1vcmFucyBpIA0KI21vdmUgdGhlIGZpbHRlcnMgdG8gdGhpcyBjZWwgdG9vIA0KDQoNCiMgU3BhdGlhbCBMYWcgZm9yIHByaWNlDQpjb29yZHMgPC0gc3RfY29vcmRpbmF0ZXModGVzdF9kYXRhKSANCm5laWdoYm9yTGlzdCA8LSBrbm4ybmIoa25lYXJuZWlnaChjb29yZHMsIDUpKQ0Kc3BhdGlhbFdlaWdodHMgPC0gbmIybGlzdHcobmVpZ2hib3JMaXN0LCBzdHlsZT0iVyIsIHplcm8ucG9saWN5ID0gVFJVRSkNCnRlc3RfZGF0YSRsYWdQcmljZSA8LSBsYWcubGlzdHcoc3BhdGlhbFdlaWdodHMsIHRlc3RfZGF0YSRzYWxlX3ByaWNlKQ0KDQojIFNwYXRpYWwgbGFnIGZvciBlcnJvcnMNCmNvb3Jkcy50ZXN0IDwtICBzdF9jb29yZGluYXRlcyh0ZXN0X2RhdGEpIA0KbmVpZ2hib3JMaXN0LnRlc3QgPC0ga25uMm5iKGtuZWFybmVpZ2goY29vcmRzLnRlc3QsIDUpKQ0Kc3BhdGlhbFdlaWdodHMudGVzdCA8LSBuYjJsaXN0dyhuZWlnaGJvckxpc3QudGVzdCwgc3R5bGU9IlciKQ0KDQp0ZXN0X2RhdGEgPC0gdGVzdF9kYXRhICU+JQ0KICBtdXRhdGUobGFnUHJpY2VFcnJvciA9IGxhZy5saXN0dyhzcGF0aWFsV2VpZ2h0cy50ZXN0LCBQcmljZS5FcnJvciwgTkFPSyA9IFRSVUUpKQ0KDQojIEZpbHRlcmluZyBncmVhdGVyIHRoYW4gMCB2YWx1ZXMNCg0KdGVzdF9kYXRhX2ZpbHRlciA8LSB0ZXN0X2RhdGEgJT4lDQogIGZpbHRlcihQcmljZS5FcnJvciA+IDAsIGxhZ1ByaWNlRXJyb3IgPiAwKQ0KDQojIE1vcmFuJ3MgSQ0KDQpnZ3Bsb3QoZGF0YSA9IHRlc3RfZGF0YV9maWx0ZXIsIGFlcyhsYWdQcmljZUVycm9yLCBQcmljZS5FcnJvcikpICsNCiAgZ2VvbV9wb2ludChzaXplID0gLjg1LGNvbG91ciA9ICJibGFjayIpICsgDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsY29sb3VyID0gInJlZCIsc2l6ZSA9IDEuMikgKw0KICBsYWJzKHRpdGxlPSJQcmljZSBFcnJvcnMiLA0KICAgICAgIGNhcHRpb24gPSAiRmlndXJlIDYuMSIpICsNCiAgcGxvdFRoZW1lKCkNCg0KbW9yYW5UZXN0IDwtIG1vcmFuLm1jKG5hLm9taXQodGVzdF9kYXRhJFByaWNlLkVycm9yKSwNCiAgICAgICAgICAgICAgICAgICAgICBzcGF0aWFsV2VpZ2h0cywgbnNpbSA9IDk5OSkgIA0KDQpnZ3Bsb3QoYXMuZGF0YS5mcmFtZShtb3JhblRlc3QkcmVzW2MoMTo5OTkpXSksIGFlcyhtb3JhblRlc3QkcmVzW2MoMTo5OTkpXSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjAxKSArDQogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtb3JhblRlc3Qkc3RhdGlzdGljKSwgY29sb3VyID0gIiNkNzE5MWMiLHNpemU9MSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygtMSwgMSkpICsNCiAgbGFicyh0aXRsZT0iT2JzZXJ2ZWQgYW5kIHBlcm11dGVkIE1vcmFuJ3MgSSIsDQogICAgICAgc3VidGl0bGU9ICJPYnNlcnZlZCBNb3JhbidzIEkgaW4gcmVkIiwNCiAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSA2LjMiLA0KICAgICAgIHg9Ik1vcmFuJ3MgSSIsDQogICAgICAgeT0iQ291bnQiKSArDQogIHBsb3RUaGVtZSgpDQpgYGANCg0KDQpgYGB7cn0NCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gdHJhY3RzMjEsIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZ3JleTg5IikgKw0KICBnZW9tX3NmKGRhdGEgPSB0ZXN0X2RhdGEsIGFlcyhjb2xvdXIgPSBxNShQcmljZS5BYnNFcnJvcikpLCANCiAgICAgICAgICBzaG93LmxlZ2VuZCA9ICJwb2ludCIsIHNpemUgPSAuNzUpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZSwNCiAgICAgICAgICAgICAgICAgICBsYWJlbHM9cUJyKHRlc3RfZGF0YSwiUHJpY2UuQWJzRXJyb3IiKSwNCiAgICAgICAgICAgICAgICAgICBuYW1lPSJRdWludGlsZVxuQnJlYWtzIikgKw0KICBsYWJzKHRpdGxlPSJNYXAgb2YgUHJpY2UgQWJzb2x1dGUgRXJyb3JzLCBCb3VsZGVyIENPLCAyMDE5LTIwMjEiLCANCiAgICAgIGNhcHRpb249IkZpZ3VyZSA2LjQiKSArDQogIG1hcFRoZW1lKCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gdHJhY3RzMjEsIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZ3JleTg5IikgKw0KICBnZW9tX3NmKGRhdGEgPSB0ZXN0X2RhdGEsIGFlcyhjb2xvdXIgPSBxNShQcmljZS5BYnNFcnJvcikpLCANCiAgICAgICAgICBzaG93LmxlZ2VuZCA9ICJwb2ludCIsIHNpemUgPSAuNzUpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZSwNCiAgICAgICAgICAgICAgICAgICBsYWJlbHM9cUJyKHRlc3RfZGF0YSwiUHJpY2UuUHJlZGljdCIpLA0KICAgICAgICAgICAgICAgICAgIG5hbWU9IlF1aW50aWxlXG5CcmVha3MiKSArDQogIGxhYnModGl0bGU9InByZWRpY3RlZCBzYWxlIHByaWNlIiwgDQogICAgICBjYXB0aW9uPSJGaWd1cmUgNi40IikgKw0KICBtYXBUaGVtZSgpDQpgYGANCg0KYGBge3J9DQoNCiN0aGlzIGlzIHdyb25nIA0KDQpzdF9kcm9wX2dlb21ldHJ5KHRlc3RfZGF0YSkgJT4lDQogIGdyb3VwX2J5KGNlbnN1c190cmFjdCkgJT4lDQogIHN1bW1hcml6ZShNQVBFID0gbWVhbihQcmljZS5BUEUsIG5hLnJtID0gVCkpICU+JQ0KICB1bmdyb3VwKCkgJT4lIA0KICBjcm9zc19qb2luKCB0cmFjdHMyMSklPiUNCiAgICBzdF9zZigpICU+JQ0KICAgIGdncGxvdCgpICsgDQogICBnZW9tX3NmKGFlcyhmaWxsID0gTUFQRSkpICsNCiAgICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gcGFsZXR0ZWVbNV0sIGhpZ2ggPSBwYWxldHRlZVsxXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJNQVBFIikgKw0KICBnZW9tX3NmKGRhdGEgPSB0ZXN0X2RhdGEsIGNvbG91ciA9ICJibGFjayIsIHNob3cubGVnZW5kID0gInBvaW50Iiwgc2l6ZSA9IC41KSArDQogICAgICBsYWJzKHRpdGxlID0gIk1lYW4gdGVzdCBzZXQgTUFQRSBieSBCbG9jayBHcm91cHMiLA0KICAgICAgICAgICBjYXB0aW9uID0gIkZpZ3VyZSA3LjMiKSArDQogICAgICBtYXBUaGVtZSgpDQoNCmBgYA0KDQpgYGB7cn0NCg0KdGVzdF9kYXRhIDwtDQogIHRlc3RfZGF0YSAlPiUNCiAgZ3JvdXBfYnkoY2Vuc3VzX3RyYWN0KSAlPiUNCiAgbXV0YXRlKE1lYW5QcmljZSA9IG1lYW4oc2FsZV9wcmljZSkpDQoNCnRlc3RfZGF0YSA8LQ0KICB0ZXN0X2RhdGEgJT4lDQogIGdyb3VwX2J5KGNlbnN1c190cmFjdCkgJT4lDQogIG11dGF0ZShNQVBFID0gbWVhbihQcmljZS5BUEUpKSANCg0KI1RoaXMgbm93IHNlZW1zIGNvcnJlY3QgdmVyc3VzIHRoZSBvbGQgdmVyc2lvbiAtIHdvbyEgDQoNCmdncGxvdCh0ZXN0X2RhdGEpICsNCiAgZ2VvbV9wb2ludChhZXMoTWVhblByaWNlLCBNQVBFKSkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBhZXMoTWVhblByaWNlLCBNQVBFKSwgc2UgPSBGQUxTRSwgY29sb3VyID0gImdyZWVuIikgKw0KICBsYWJzKHRpdGxlID0gIk1BUEUgYnkgQmxvY2sgR3JvdXAgYXMgYSBmdW5jdGlvbiBvZiBtZWFuIHByaWNlIGJ5IEJsb2NrIEdyb3VwIiwNCiAgICAgICB4ID0gIk1lYW4gSG9tZSBQcmljZSIsIHkgPSAiTUFQRSIsDQogICAgICAgY2FwdGlvbiA9ICJGaWd1cmUgNy41IikgKw0KICBwbG90VGhlbWUoKQ0KYGBgDQoNCg0K